From d8efd1b1adb70e9469edc7e072af0304fcaa81d5 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Tue, 3 Jun 2025 16:13:42 +0200 Subject: [PATCH 001/141] Add Play button and grid size change button --- data/images/engine/editor/grid_button.png | Bin 0 -> 245 bytes data/images/engine/editor/play_button.png | Bin 0 -> 1692 bytes src/editor/editor.cpp | 23 ++++++++++++++++++++++ src/editor/editor.hpp | 3 +++ 4 files changed, 26 insertions(+) create mode 100644 data/images/engine/editor/grid_button.png create mode 100644 data/images/engine/editor/play_button.png diff --git a/data/images/engine/editor/grid_button.png b/data/images/engine/editor/grid_button.png new file mode 100644 index 0000000000000000000000000000000000000000..b7528d41927e4b4bc6724c443066af16f13ea17c GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@HeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)pVryh>nTu$sZZAYL$MSD+10g0sLQ zvY3HEPZ@+6E0)@q0R`DhJbhi+U$Qat3TXG8IrI!Dr9hW>v35PjADs^V>z1!F+kWQr^1|Evf=XZJ2((;0#Ua>a5GASTB;cm4?80#< gBejL&;c5fFgAO@{ck)7ofW|U-y85}Sb4q9e0J8T;S^xk5 literal 0 HcmV?d00001 diff --git a/data/images/engine/editor/play_button.png b/data/images/engine/editor/play_button.png new file mode 100644 index 0000000000000000000000000000000000000000..adaa3d27655d510a6b32c158840f319452b568d3 GIT binary patch literal 1692 zcmV;N24ne&P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk1_KQyD4y8@DF6Tk z@<~KNRA}DKnrmznRTRg6=k9DvZ3~FD6t^mB6$==#V|hhEjnPDn3B{<;CX#4E4QTk_ zg9KwVwulB3d?33qiWQ=$!ACS=f>CQC6?O|K)>R`~En;b%nd=9ZLf^C9>C*ql z-8*Og|GDR!d(XL7NDy20m?B<$2#W>g0keSVKo)RLY*+`-2-E^KKu~L4*<eb65?vqdNdmH`ETCr%Rbfxz;NMq}DoP#!*1AZ%KiYujw5>Om&fVYTnt(vioQ1FrxQ685pw^U>7FO4cG*CGp zzZ9V=iR1z1h)@*>ns+56AP_QFBfJY_l6+($?C^)ohejnJ5Hwc8Z>w2rzd#6ok=%EIcM zBMJzF%-O&mCvKDK$>E;a?=ZpWJM>uVZ+um|o|ecDZi;Btng#s>yN?TP*;kMXyyevA zeHk|oAP+D#Gmj@td3=vt|M}R+AkPWOD%cSm6UQFK-?#Qbk!#gukb1VG8f~;NvB2ogRCu4P*HgN_; z`J2g`d|x7F(qo)a{wh;qTjHc)f;W>JFI&Y87j43;ri|9ivRC(BElLb>lf2J~g`DDw ztm{~EWd+&i+&CH&1xk5x6~f|C6eF%-a@sWRm{HE0?8lJ!Xm|u!q=31cWf&@jxfkBe zZFyTs_g)^)G|p488g^X3H)$qC`2jAOwA|?8 z4_DCDVbR(=WO3B4VjZ(_h2M`J;5Lo!;6!7TNS8g>&_;uk?Y>LL1Mz52HX!aRe>q-%^&c=SIy{m;?!PhwW z*Xxd-Y5FHZ&LtD@7-9jHhGknv43BXDxHg&OYwLevXVp_Qcl;P%f5p-?)kiqrm%3kD z_TOsNyze#ecCv`~SP{Pd{ypCKa#`HuPocBT;`q_-9S_bwpp96RZx`X2v98LRTI%_* zY9oLA`&q)?*V%4u?=2}y3#%J}H^(keHHUuxmd(5Fb%Q)WMcMLj1AuYHOE!pzn}-%B zr@On2&qG_OuHE96$u?1J7|>QJE!=+uPXFQP%QF1&R$ard_;;1MNgjmf%9e+FPjUJ* zX>6N~O0m>wjeNZG7c@4~ugPs$QTF`wDZ39njjbbYPUt}qHulxi5B{JzON2eZL?;%n z_q{-V&f-3su#dmn#BU8BxgAQaA}lN`4*xi;0DwTqTnfDFWO374$nLq9+{v>!(cZ{+ zhu@$pTJJV_3t?G#arpg#9yJ64A#)Y*5)O``qTM$2wOzyR+Y?D*z?$-s@JmD01A}7a zwbh$}hk>4v-r!ADOwLXj3-Z=*|vgn za&l3KaBqX0000("images/engine/editor/grid_button.png", + Vector(110, 10), + [this] { + auto& snap_grid_size = g_config->editor_selected_snap_grid_size; + if (snap_grid_size == 0) + snap_grid_size = 3; + else + snap_grid_size--; + }); + + m_grid_size_widget = grid_size_widget.get(); + + m_widgets.insert(m_widgets.begin() + 2, std::move(grid_size_widget)); + + auto play_button = std::make_unique("images/engine/editor/play_button.png", + Vector(160, 10), [this] { m_test_request = true; }); + + m_play_widget = play_button.get(); + + m_widgets.insert(m_widgets.begin() + 3, std::move(play_button)); } Editor::~Editor() diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 6bd4f173fc..9f65437c44 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -225,6 +225,9 @@ class Editor final : public Screen, std::vector > m_widgets; ButtonWidget* m_undo_widget; ButtonWidget* m_redo_widget; + ButtonWidget* m_grid_size_widget; + ButtonWidget* m_play_widget; + EditorOverlayWidget* m_overlay_widget; EditorToolboxWidget* m_toolbox_widget; EditorLayersWidget* m_layers_widget; From 796038ac18ea1e492122178c793e2476e9e398d9 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Tue, 3 Jun 2025 16:51:49 +0200 Subject: [PATCH 002/141] Add offset to tilemaps in order to have space for displaying toolbar at the top --- data/images/engine/editor/grid_button.png | Bin 245 -> 228 bytes data/images/engine/editor/play_button.png | Bin 1692 -> 1241 bytes data/images/engine/editor/redo.png | Bin 2085 -> 1373 bytes data/images/engine/editor/undo.png | Bin 2059 -> 1367 bytes src/editor/editor.cpp | 8 ++++---- src/object/tilemap.cpp | 4 ++-- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/images/engine/editor/grid_button.png b/data/images/engine/editor/grid_button.png index b7528d41927e4b4bc6724c443066af16f13ea17c..1eecfd4e12da342e495ba1b1750cc415eebd6aba 100644 GIT binary patch delta 131 zcmey$_=HihGr-TCmrII^fq{Y7)59eQNGpIa2NRI|Bk^|eM8&K~J`EM_Rd=rfMXWqs z978;gCnrd}Y7lh#`~Sauw~Vs#(;%NM-wv)|RX)JfcCp87)mdKI;Vst0OH&;YybcN diff --git a/data/images/engine/editor/play_button.png b/data/images/engine/editor/play_button.png index adaa3d27655d510a6b32c158840f319452b568d3..879ed678c20b35b3215abb6d2afcc01dbdd2d104 100644 GIT binary patch delta 1152 zcmV-`1b_RS4cQ4HiBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPQIe-0@E zz`SW`000CpNkl_+->r z9@=P}HnFLR_@*z)q%pBl5|eg(>4OhyOw+_@icy+qTWEj-a*?q~5z=zMLrY<9XZygB za2sZZi~sA{d$0fOz5ccSC5Ql*f9f4lN-}|5;0RC%WCO|Ol5yZ3&;Z;3+Kd3*XAbB_ zxU)sL1@1=2Zs22}2uKHHm@Nj-3)}`=&V0RnO$6Kx4m*N#KsgY@+TaB)pt$HP(1#-? z;BIj2L2w1w7s)M!fdZOQoN^ZEtzikc8yyFM?}3cvC;5>g4nxf#(4r;se*k#XnXlJ{ zB;amvyolhZm4aKPox-iEB#KMgJ0LTlqD>*h}J~%*HQVu20eM(NsSwvW2 zEW$o9m)y(~Fs1h4Y6&M+fA|z{dy?YJlN6_aPP}y&5Ydwepxlg!%@RXF#_Js0QAwh0cQ_L1K&}LiMo?ZOe})tm(vn`_wOv(Y zC7nbFEff(69Faf)n}B#*GKY7($Ki}Gv00uCwX9H@U2RjK$ryIpizvye#45M0v?NO| zXsMfqrxUYDvmHmNz_Q$x{0|5kL57D0nVnWE>Tw*fZ&q+$z{}0HYy91R6^j= z=8M{xW;&S-C!kP_e@+f?z42@A4txtp0)7}D@UbN=1~JE>Q!{PU!3_w9osz%B%6eE4U-X~JDcFi#8f0mNk^$_s-W@zcWNp(vF zzTikiF3#^YZjTT9y5*Td-2f^~0~s~OrUtostCAm^&Y8g@G^+yFXK#573E<4v{{p^P zt$PHNf3dcM0q)+{asP6Pj{a|=@vaKN1=k5r`yxlA6jeY|NHaIpfH&}XE|a~FxZZGu z?`qF7?dyuxe>gV-7gsi=jgCFQ&nuZyP{aogu|@2~(^tdjbkka1tOr4<>x8FuHCukb zwB_5D>)T+YZ-$wNu!eD=2YAC(=BZoSlNZ66uirs&9MG5Mgb(+T|A!W{byctftP{(Mo?u9J@BUZW(|Oe1><=Q(2k_b^|mMMc>XUac5qM7 S$O6#-0000BC3mCCuc|}2u(L{|2#i-CG zl4wE=X!ziR1YJxBQEMUj#!X z-?QE6(*MWZJ7@m?x#ygF&$(Afe-K;tm?B<$2#W>g0keSVKo)RLY*+`-2-E^KKu~L4 z*<eb65?vqdNdmH`ETCr%Rbfxz;NMq}DoP#!*1AZ%KiYujw5>Om&fVYTnt(vioQ1Frf4GL^;Gou& zlonRkj5JU=Aior$Dv9I)=7>-g2%2{#Bp?tnS0lU&WRiSjBJA*o%!fuLAP_WH11~zs zE`o;)Z$5^ao1cK&W2s@mR&&{}#>n?tf%+&LU+TGL~`+`vYdbEzZ zDa1l^&&tB;og)eegv{B%e;y}plj_Ogp4sm(!RR~mSnF?mRlA;+$PaFcXw{kp{R6v? z3vJm~kP5uz)aQK}HxD2WFf}ueCHXHiajR_I9#W)TwKq+}No^0b)sYB0>kCo5`DeUm|AGW1La`DpO)x;-q1MH&-tGwxKlW+X*Z$7Acp1P*p zgNuvUG&e?>e=;@Y%%5uvVBgs!uC>jiMv0BY*t+S;F4e*=}v`Eh$S2s~dqg$1YGchkpN-&AaY(gFHY* z+467$fN{o4Hi(FuhZZNNySt6gLtCk?-Qt$XHc@OC&{ioe+V zca^$He;$PA%9e+FPjUJ*X>6N~O0m>wjeNZG7c@4~ugPs$QTF`wDZ39njjbbYPUt}q zHulxi5B{JzON2eZL?;%n_q{-V&f-3su#dmn#BU8BxgAQaA}lN`4*xi;0DwTqTnfDF zWO374$nLq9+{v>!(cZ{+hu@$pTJJV_3t?G#e{uNzfgUvk0wHr1@DdJ=p`zV3^|f8Y z?%NYdV!)d6lJHAI)dPcK<+atDfro*fk>21xIC& zf7q89yj5lh=S@q z2$&Dd1g1y3?X3FRuJl-H?WEsz8{lg}9oLnW{G3qo{U4~60)jq2jQ;=t002ovPDHLk FV1gyP60`sS diff --git a/data/images/engine/editor/redo.png b/data/images/engine/editor/redo.png index dd4ab3d51eb3dc45ddae16242109efed143a297c..d3467babfb4f2bca4b100882886b17b946e6be8a 100644 GIT binary patch literal 1373 zcmV-j1)}z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vGf5&!@T5&_cPe*6Fc00(qQO+^Rk1_KT&6>(XPEC2um zvq?ljR9M5+mr-a_cNE5dH;w7K)Fx82B?XHOj4;?kQEOMa7E$KI5S)k)+rys5CR11T z+=sxXon^kv%_5HB1~Oy??Yb5=6r)Vn+A`J#TbWp0Q)}wl|wW(+BC-5 zKJ0@Fmz#h7-}gWFobQ~g@N7_W?u8YA@{DWW0GfdS-~bZ9HQ;+-cC`e!fd=4R;PqX* zc2!nYRZ&w@Lo^ztr>BQlEM@{XfKP$nfT=t@n}Kpb1%5A9pbz*CNNn4-O-`OXDU*|v z5|76vl}d@Ks&en%J-K}OvIGJF0ZiZz;JutZTtFKTtEi|@fqvlSV#r?serjlF$eucN zN>o)95fRI>y!0Pib`l00_om|VSjRm#fBRG{T)2Nrrr%#`LF7o9tJUlEVB_%5G>H?u7hx|q0t$q9Ut>3wGC#!*2EQV!SDce5) zaOV783p6z~H934fABwWPMTiIw9z4J>3=~Dd>-FOGdW#YvA{LO%7lHcn@^bIity^)q zT+41?Sr!*ATww3sz0}p!v1!vL+S=Nfoqf6y&dtp&Sa@C=|E#sOHS_S{!-AzYHZ~^J z)zx9!_3D6c6-CK}LZO1?DI(I<)g{0fP?awNUZ2nBT(@rBvM0!7G8{Q_glIH+184_! z1G|I4ptHHTd8yAo@fvkq2Y3X`=gWaL8#Zjf>2&6A8j3<+Ums`Bo}B_ZfwzHAjvhT) ze(>NyN=ix!hCh?ZpsFfB5=i9}P!Q>KnxUZ~fIhqPeb~{_QGV#qA>3|v!T%jB@Rxl* z2}JBG3Z#KQmlH5KI5_wy91d4(+_-T`0;SVwrl+SBySJObH})WsyU+v-z$$iP%B_MB zaO>j5izE^WL}W=4I{(ek%{Hk8Xm(O9T#sxKMUQK_0%E`q-QC^B(9jT;Wi47*T3SjV z5CCWeDp)0WfSG!Oe``H(=_GGnKJ^!3w5)VKqD~G-rk;#$K#7cOifKmWo4xR zAFUKwKKFKU@_)oX0NQ{rIJH5AtG^>+52&3&SmBdxTkfkWVb}2WR$J=R4h23S_t}H z@Y0Hpb^>Y5HfIu(N&X$5ceqr&ji_G#?`t|bvpHJ>SDWytW?E(8apCFJvZ;7WxJN6d zv%-hMVM`7ue5LeEou3sK>in+QO2#L^t$GFvzc}-YwW3uL9uf|f^osU#9(5Cc&X`L` z?UUJ$O`IuW1j$fg8J#i_sfJ|M+!w9+W?2!BCV!JPEO8VliZ?83MY`ss=0(rRr?aMV z|C^_s9~rK9_s78dSyVR#SL!ZqBrBzxQlX0oiim}VDg;r{sUXQhF&0F}8WST` z@%9uXnt6d^9)EA3pA5?wtF7=YO34KbNsDTr9UVPOR$M{Us5A@{NQ{ zLMb7P5J`w2ED^>DQ-q(*X1!EG17Q#|9vd6W0Xh$cGs2&QZ$fwnCd014tylnxe@FO% z@D;&IinGSXMwXeG$&!+iSYBS9^x&H}Z&+_{FMIXsm3oX_!motyuXs-!p-bhS-x4-& zGV29|F~TWkH8nMrH#9Wx!NEc9cDs2nIX*t-Q&Us?$&)9rgooght%6vo)p$=fVF=HJ zg@tju-HvBR2_ppCb(OCa$_vGpmzTNM>y?xgM?IOJpXU`76+V@Fb!jOd7Z=C-`uh0D z$OyODY=T6>b(MD!4hsqj_}JJOKRrF=p@_%hxj=GhX-OR;zst(^;5s`yn-2{QNzaas zj`D1Q4(j@KuMy;d8lM zhFv8TdG_pCfCOe z4IzVqmK7EjG78wwy5v*rF|tsAiynbALVik0imj%mhGk@AF!D|IxrIE5$=OiX0@axP zw6rvqpP$bnBO{r`Vln!H3)kv?t6F)@)vMMX)# zWW0O#&MZ52qcHo8P(`&3YiVg=4<9}>6iaGL5OHz3s7JuhA~n?z0pSjvot@&=AA}#7zZq#K zl+w(zj*bpiRaIp;<0wh2uC6KxZ0r5E8K!D(ZZ;gaD63I!(Em1=zj8&|>SJ;}FG}L; zR;wEu8=MG<@nh5qhu<_mfB5i0nkACA@gr;prKQ&z7h}Q;K7RbDOr1xD z2;k2sAHu`+`B9ZBPF-@gk(zJ#J9=fO)2aDCln2T@KRG!mWt+vtMag9V#FjtN@pC#x zYmKAU5DJ{q;8AO9Yb-rIU6;V#-X0Dpmi|)S-`w1EmY0`1s1*#{BbShrMPY`cfz?_x z@Mu8?v=%Wn0hjD+>7;XWBamK3Ull`N)}0d2nSaS6#zlr*VSRx4yov zu0t+~oZ{f0`Kq6e6|<-W_Jj@y=j`9@6?R($B;5(?2r^hf&rcO`f+Fw%`;SGd)WoJ$ zq1)}wq%2^0`t+$KCMG7Z>M8}+LFRb<`Zc5Vz$ORU8Z&{QrTiP&rM0zH+F8?*^Ye55 z>C-2Uy&~Ban#`cQkXHCHt+HP11wq+h($v%>2w)dF#ViC+*kLR2n0jO1!oq?k3G@uu z_oNn7WTG51Ss}2c*w)tOIXF1bv;f5l&&kOVR%ka90pUu>>zh>D_-N(Ttl8V!+m+t| zvdl)JOD3^TB&4l$H3{slWo2av5;JBaAV^^I)r-uuwY7E03Nte^${yIsf)rq8Ai?YF z>-p~P?xk-7RE8)?;QLs-8Dj$F0CzYXoJtOkU2Cirg3$2ru(U<4B4ITXiF!c-5Z?_D z;G)q-lP~Wm1HZ`bbn7IpyJz@T1}f2ev$L}?0|NsVB4DLpmdbGS!gjlzQA>$F-~qBN z^EdoHv$>S`c81QzlboE)i;9YPcXv0(;#XH!$Eh`ZPrubM|F>gPiuh*S&-`B=CD*}+ zIVuF-Q-8c*b^=0@nSXn-o3Nk&l)rZamklbGJ0xRMjgKz@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vGf5&!@T5&_cPe*6Fc00(qQO+^Rk1_KT%Fe655DgXck ztw}^dR9M5+mtSmCRUF4ZckMd2mTe4SP1!VzU=|aDQy4nO76Na?5DAi~4|`L2fnj*i zu!klJ$RaEeWs>`&8@b-io5-L>7lKVL4S>)Kgx zeej#ySnc_ji8hcNJDoO3JxB%}YV_rr;%Y8a_`wNXYQ;uxOelPbfO6{Wwm7(hIDmxNkKtD0C+3av>@d?Vi?B4%*+h=(4IYeIB?*= zdf;84Bon}*h#0wcsH!SsV`Getj^g+G5y_r+6h*=1a?#MxPyjRl&u0P zN#Hx62Ke0Rboz#dhE`mn4m;>fDO1u3j#O-!lfj7;gEf5IY zIdS3y$z(F?EUaC-7MIIqHPihvfCV6`s_KfJ09Y&*jvhVA$&)9`fzN>Vfc8t5E=~6K z_GW|zuv)Eb*suYhFk=t`YAhBLRaJ>bqbowJS+j=2hYxe=)F}sW1egYT2L}g9BoaBV z4r|)={MoZ-6CE8LR)@o3sjRGI%a$!{-MSTr!vVnN%}Xn{EIaV?wr$&FVq)U4c`O!_?(S|ezxS8< z8hJ6$1L$6_S9Dz$!!ShCG&ytTjBMPvQ3JlpkULf_>VK@OtCQ>3uS+tSlu#%neSLj$ z@ZiBmW%;N18o5PLlp}?Og|VKV9?>*SZr;2p$B!S^tE#GEz-{22)J-SXy&&gbIuUX1 z-Md#WDJh|)r3H`2lbD^I9R+>>I)GX7D2t*fjlds(ALs{KfEUuzbY6c>ttnH5AtG^>+52&3&SmBdxTkfkWVb}2WR$J=R4h23S_t}H z@Y0Hpb^>Y5HfIu(N&X$5ceqr&ji_G#?`t|bvpHJ>SDWytW?E(8apCFJvZ;7WxJN6d zv%-hMVM`7ue5LeEou3sK>in+QO2#L^t$GFvzc}-YwW3uL9uf|f^osU#9(5Cc&X`L` z?UUJ$O`IuW1j$fg8J#i_sfJ|M+!w9+W?2!BCV!JPEO8VliZ?83MY`ss=0(rRr?aMV z|C^_s9~rK9_s78d~Mf&OP`1J)(cPME%3hOZK^b6UhYbFg|Bg zF+O298K(>vW1O+d=nMr3Gs)Hv6Tt7jWc(o&6=q0IPBtJFA7!*L@3|&mK$Rfn|!}n5)@LAvl};Vh`G5raeRC%tb`R8qobput*uSu<>jeo zoG_*tWx){0gz^aq31VVmLJSWNi`3LqD-r{`Yp}K5Po6vxcEsg!c|X_I)^s9@M14>M zGBY#95bQCwWC6djTze$ka@mv~RPv$Hc1h@709i0SERQC3!l^$rFKEmm#nX^>xVla5(tBnDJdzY1Rw_o2i`*% zPMMvZ)k*YODID!{jVwuT-@dhE$mA$GIy&4X2bGtX)4hB5)MbU$q4w3p(3~wy?2B}f z!iU;OfPAQNS-@yweVYt%Uf9x z_a8ods4h`eek*;qoUzG)VIDpms4HE&c8#uHz3PWc*C;s@Qpi z>Z+*anJ;M-y}e!^20#V}7oYmUMDk)x`8?xK=(WGUf60!OH#av`ZFWLF zO~2Iw-_W~>MIw3n1fWQ|LAms2RVq06tdP^`v`pNL2F8wd&&5WGAwRPztgr3fw%k3x z1k^)~4Ukx4EXaGkD$htjk$CxOcCL;k^m8UwR#yCmAnY!7eQL_s#2ve5z^`75J5bmhvG54sKj>r4ONB9qSSA_#nn9Y{+{ zGa`^x)r&K=wY5bL9y|~nfzG7$FSg5)(hdV-2K3^^3&989G;v|F*VWYp_k{9)Q4qtf zPzqYYIS^=XZ@0|4AcYwbXKR-{{9TctJfs8>h+9Nserh(|fbz@B%NTIErScdx=0yqt z6m_Dcq(pEvYHI!0uV0J%_wU1Pf*LV~Lkda0H|Xx}HtjVyGYhVrVEG-1h;bdrB9t#I zEIc>WgYp=;v+`>83grhPMR8>4DUwNVadGjS^*LFoh~tt3B1Un~ zUHR5XaT_#2E!-0K_XnD5BfNeGk2GEDZ-N4&@gI{|LQpSHBL7)fbxa8OiM zR*IaQ990_ZQ;_e%!cKrYv*o0uq^EdcsJRN>x{Oee?}o!IXll^KwUB2Ns4z&Lv0zRp p9~Kox5^>tKp!Ykn|K&d;e*@}-^pIh|h>ZXM002ovPDHLkV1goh<&XdX diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 475a3b7855..37ff598a2a 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -146,7 +146,7 @@ Editor::Editor() : m_widgets.push_back(std::move(overlay_widget)); auto grid_size_widget = std::make_unique("images/engine/editor/grid_button.png", - Vector(110, 10), + Vector(64, 0), [this] { auto& snap_grid_size = g_config->editor_selected_snap_grid_size; if (snap_grid_size == 0) @@ -160,7 +160,7 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 2, std::move(grid_size_widget)); auto play_button = std::make_unique("images/engine/editor/play_button.png", - Vector(160, 10), [this] { m_test_request = true; }); + Vector(96, 0), [this] { m_test_request = true; }); m_play_widget = play_button.get(); @@ -1085,9 +1085,9 @@ Editor::retoggle_undo_tracking() { // Add undo/redo button widgets. auto undo_button_widget = std::make_unique("images/engine/editor/undo.png", - Vector(10, 10), [this]{ undo(); }); + Vector(0, 0), [this]{ undo(); }); auto redo_button_widget = std::make_unique("images/engine/editor/redo.png", - Vector(60, 10), [this]{ redo(); }); + Vector(32, 0), [this]{ redo(); }); m_undo_widget = undo_button_widget.get(); m_redo_widget = redo_button_widget.get(); diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index 0b9f8d067e..234d1f0f23 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -53,7 +53,7 @@ TileMap::TileMap(const TileSet *new_tileset) : m_width(0), m_height(0), m_z_pos(0), - m_offset(Vector(0,0)), + m_offset(Vector(0, 32)), m_movement(0, 0), m_objects_hit_bottom(), m_ground_movement_manager(nullptr), @@ -87,7 +87,7 @@ TileMap::TileMap(const TileSet *tileset_, const ReaderMapping& reader) : m_width(-1), m_height(-1), m_z_pos(0), - m_offset(Vector(0, 0)), + m_offset(Vector(0, 32)), m_movement(Vector(0, 0)), m_objects_hit_bottom(), m_ground_movement_manager(nullptr), From cd6390405ae46a6ee373cb6c6fb1ee9eab319907 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Tue, 3 Jun 2025 20:13:54 +0200 Subject: [PATCH 003/141] Fix offset calculations for tilemaps when saving --- src/object/tilemap.cpp | 33 +++++++++++++++++++++++---------- src/object/tilemap.hpp | 4 ++-- src/supertux/level.cpp | 7 ++++++- src/supertux/level.hpp | 5 +++++ 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index 234d1f0f23..9e4faeec6e 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -27,6 +27,7 @@ #include "supertux/debug.hpp" #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" +#include "supertux/level.hpp" #include "supertux/resources.hpp" #include "supertux/sector.hpp" #include "supertux/tile.hpp" @@ -53,7 +54,7 @@ TileMap::TileMap(const TileSet *new_tileset) : m_width(0), m_height(0), m_z_pos(0), - m_offset(Vector(0, 32)), + m_offset(Vector(0, 0)), m_movement(0, 0), m_objects_hit_bottom(), m_ground_movement_manager(nullptr), @@ -87,7 +88,7 @@ TileMap::TileMap(const TileSet *tileset_, const ReaderMapping& reader) : m_width(-1), m_height(-1), m_z_pos(0), - m_offset(Vector(0, 32)), + m_offset(Vector(0, 0)), m_movement(Vector(0, 0)), m_objects_hit_bottom(), m_ground_movement_manager(nullptr), @@ -393,7 +394,7 @@ TileMap::after_editor_set() } } else { if (m_add_path) { - init_path_pos(m_offset); + init_path_pos(get_offset()); } } @@ -677,15 +678,27 @@ TileMap::resize(int new_width, int new_height, int fill_id, apply_offset_y(fill_id, yoffset); } -void TileMap::resize(const Size& newsize, const Size& resize_offset) { +void +TileMap::resize(const Size& newsize, const Size& resize_offset) { resize(newsize.width, newsize.height, 0, resize_offset.width, resize_offset.height); } +Vector +TileMap::get_offset() const +{ + // Apply custom offset for tilemap to fit buttons at the top of the editor. + // This extra offset isn't saved. + if (Editor::is_active() && !Level::current()->is_saving_in_progress()) + return m_offset + Vector(0, 32); + + return m_offset; +} + Rect TileMap::get_tiles_overlapping(const Rectf &rect) const { Rectf rect2 = rect; - rect2.move(-m_offset); + rect2.move(-get_offset()); int t_left = std::max(0 , int(floorf(rect2.get_left () / 32))); int t_right = std::min(m_width , int(ceilf (rect2.get_right () / 32))); @@ -738,7 +751,7 @@ TileMap::get_tile_id(const Vector& pos) const bool TileMap::is_outside_bounds(const Vector& pos) const { - auto pos_ = (pos - m_offset) / 32.0f; + auto pos_ = (pos - get_offset()) / 32.0f; float width = static_cast(m_width); float height = static_cast(m_height); return pos_.x < 0 || pos_.x >= width || pos_.y < 0 || pos_.y >= height; @@ -754,7 +767,7 @@ TileMap::get_tile(int x, int y) const uint32_t TileMap::get_tile_id_at(const Vector& pos) const { - Vector xy = (pos - m_offset) / 32.0f; + Vector xy = (pos - get_offset()) / 32.0f; return get_tile_id(static_cast(xy.x), static_cast(xy.y)); } @@ -789,7 +802,7 @@ TileMap::change(int idx, uint32_t newtile) void TileMap::change_at(const Vector& pos, uint32_t newtile) { - Vector xy = (pos - m_offset) / 32.0f; + Vector xy = (pos - get_offset()) / 32.0f; change(int(xy.x), int(xy.y), newtile); } @@ -1021,11 +1034,11 @@ void TileMap::move_by(const Vector& shift) { if (!get_path()) { - init_path_pos(m_offset); + init_path_pos(get_offset()); m_add_path = true; } get_path()->move_by(shift); - m_offset += shift; + set_offset(get_offset() + shift); } void diff --git a/src/object/tilemap.hpp b/src/object/tilemap.hpp index 0c3edd69a9..5d54a79837 100644 --- a/src/object/tilemap.hpp +++ b/src/object/tilemap.hpp @@ -100,7 +100,7 @@ class TileMap final : public LayerObject, inline Size get_size() const { return Size(m_width, m_height); } inline void set_offset(const Vector &offset_) { m_offset = offset_; } - inline Vector get_offset() const { return m_offset; } + Vector get_offset() const; void set_ground_movement_manager(const std::shared_ptr& movement_manager) { @@ -125,7 +125,7 @@ class TileMap final : public LayerObject, /** Returns the position of the upper-left corner of tile (x, y) in the sector. */ Vector get_tile_position(int x, int y) const - { return m_offset + Vector(static_cast(x), static_cast(y)) * 32.0f; } + { return get_offset() + Vector(static_cast(x), static_cast(y)) * 32.0f; } Rectf get_bbox() const { return Rectf(get_tile_position(0, 0), diff --git a/src/supertux/level.cpp b/src/supertux/level.cpp index de92a92bcd..eff00afaf0 100644 --- a/src/supertux/level.cpp +++ b/src/supertux/level.cpp @@ -54,7 +54,8 @@ Level::Level(bool worldmap) : m_skip_cutscene(false), m_icon(), m_icon_locked(), - m_wmselect_bkg() + m_wmselect_bkg(), + m_saving_in_progress(false) { s_current = this; @@ -174,6 +175,8 @@ Level::save(const std::string& filepath, bool retry) void Level::save(Writer& writer) { + m_saving_in_progress = true; + writer.start_list("supertux-level"); // Starts writing to supertux level file. Keep this at the very beginning. @@ -220,6 +223,8 @@ Level::save(Writer& writer) // Ends writing to supertux level file. Keep this at the very end. writer.end_list("supertux-level"); + + m_saving_in_progress = false; } std::string diff --git a/src/supertux/level.hpp b/src/supertux/level.hpp index ea5476b4f3..d5bb64ac3a 100644 --- a/src/supertux/level.hpp +++ b/src/supertux/level.hpp @@ -63,6 +63,8 @@ class Level final int get_total_badguys() const; int get_total_secrets() const; + bool is_saving_in_progress() const { return m_saving_in_progress; } + void reactivate(); inline bool is_worldmap() const { return m_is_worldmap; } @@ -113,6 +115,9 @@ class Level final std::string m_icon_locked; std::string m_wmselect_bkg; +private: + bool m_saving_in_progress; + private: Level(const Level&) = delete; Level& operator=(const Level&) = delete; From a619be82604472b3515b0398719d4f1628d4abf9 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Tue, 3 Jun 2025 20:45:54 +0200 Subject: [PATCH 004/141] Include "Don't show grid" in grid toggle button --- src/editor/editor.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 37ff598a2a..e8442df234 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -150,7 +150,17 @@ Editor::Editor() : [this] { auto& snap_grid_size = g_config->editor_selected_snap_grid_size; if (snap_grid_size == 0) - snap_grid_size = 3; + { + if(!g_config->editor_render_grid) + { + g_config->editor_render_grid = true; + snap_grid_size = 3; + } + else + { + g_config->editor_render_grid = false; + } + } else snap_grid_size--; }); From 42d8a84a8fe9182456bf4714a4cfda212c690c85 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Tue, 3 Jun 2025 21:52:01 +0200 Subject: [PATCH 005/141] Add save button --- data/images/engine/editor/save.png | Bin 0 -> 1562 bytes src/editor/editor.cpp | 8 ++++++++ src/editor/editor.hpp | 1 + 3 files changed, 9 insertions(+) create mode 100644 data/images/engine/editor/save.png diff --git a/data/images/engine/editor/save.png b/data/images/engine/editor/save.png new file mode 100644 index 0000000000000000000000000000000000000000..21f39b87a352ccd463765e8db06099f9498d3a47 GIT binary patch literal 1562 zcmV+#2IcvQP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vJ)9smI)9s$tpzgqwR00(qQO+^Rk1_Ki(B5|)&3;+NH za7jc#R9M5sm|bjJdo0^JKChnhJOAh7eU8=X)2Bz3Qb$?GjvqhH;NT#g%v#ID#01`ZwALIrZ~%aF z=gzIQ{F;b7eBr`{awor`_x|Wc3-b9q*=)8WV5wAMbab?JeDL5w^7%a8dqm{+<@W&( z0v~k*bk4O7lu`g|IqYa6Lc6b~Ip+`&f*@G){H6f4)&O|#S6%qK+D3p{>+J(>bfN76 zc16Kvh}(bbmV(~t-Lc z*0y|a#qXL5Hgj<^u&GpvAP7j3gmgMh7>27-?)rWALj)XF!cB1IGec#QMJiDzLk=8H>}XD*;=ONm|D8K`V$4?QmMN!<|;u@Sb+>sDnzM4l$U}oUu1qddUN!_lqjszp663j9HJx*?ITktRC>D!Ah*A$A5+X835bVJF z7+5O>LXwzT(JZiv{GoT*uS@r;(U#-XR0_aI42nEsZ?#}9Bfx`*REZg zE{qp`^$8!2#mLI*@Q-&=UTUbNbpuc@`E>!k_sq=9Y#Q*HpXd1c^;xbghnzaLmoU^UR+3h;)>@%dLroN9GASP1A5omI zF*WT_>3fmb;Hr~V5%<)(AqFTE3Y!Kj6yVQia=d(IjEi3s`QhL_t>L2qMhjB$?Nemb z6Y+!oKIZ0UsW1HJ7D?stZ^nK-8an5QqNoMdR8T}Hm&;q~e}RShl2o3U9{ z$mdrb0|U1~%F2fVXFZcs3*4NWxFPn|@XYwjm)ieXzD2I zlPINl>4n4m>Ggl}>2=TU-PzlAD8V|<^vojF;`lXe?PoKWUzuFp7l3lPTmbHFZ`1(& z{rz1T8eV+Db;UxRI_dK3#xqyPW_ M07*qoM6N<$g6#?6asU7T literal 0 HcmV?d00001 diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index e8442df234..e678ea448f 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -122,6 +122,7 @@ Editor::Editor() : m_redo_widget(), m_grid_size_widget(), m_play_widget(), + m_save_widget(), m_overlay_widget(), m_toolbox_widget(), m_layers_widget(), @@ -175,6 +176,13 @@ Editor::Editor() : m_play_widget = play_button.get(); m_widgets.insert(m_widgets.begin() + 3, std::move(play_button)); + + auto save_button = std::make_unique("images/engine/editor/save.png", + Vector(128, 0), [this] { save_level(); }); + + m_save_widget = save_button.get(); + + m_widgets.insert(m_widgets.begin() + 4, std::move(save_button)); } Editor::~Editor() diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 9f65437c44..a2a30cac44 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -227,6 +227,7 @@ class Editor final : public Screen, ButtonWidget* m_redo_widget; ButtonWidget* m_grid_size_widget; ButtonWidget* m_play_widget; + ButtonWidget* m_save_widget; EditorOverlayWidget* m_overlay_widget; EditorToolboxWidget* m_toolbox_widget; From 00de7938fdbfb39773cf5b135c75c51e43bc1826 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Wed, 4 Jun 2025 20:31:53 +0200 Subject: [PATCH 006/141] Improve logical statement --- src/editor/editor.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index e678ea448f..f79a8f0a2e 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -154,13 +154,9 @@ Editor::Editor() : { if(!g_config->editor_render_grid) { - g_config->editor_render_grid = true; snap_grid_size = 3; } - else - { - g_config->editor_render_grid = false; - } + g_config->editor_render_grid = !g_config->editor_render_grid; } else snap_grid_size--; From a57f6da5fceb4058d9a5a8a3514a5ec4b2fe2a17 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 15 Jun 2025 08:48:29 +0200 Subject: [PATCH 007/141] Add button to switch between tile and object mode --- src/editor/button_widget.cpp | 13 +++++++++++++ src/editor/button_widget.hpp | 3 +++ src/editor/editor.cpp | 14 ++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index 0fd91ac84e..f1a449408c 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -120,3 +120,16 @@ ButtonWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) return false; } } + +void +ButtonWidget::set_sprite(SpritePtr sprite) +{ + m_sprite = std::move(sprite); + m_rect.set_size(sprite->get_width() * 1.0f, sprite->get_height() * 1.0f); +} + +void +ButtonWidget::set_sprite(const std::string& path) +{ + set_sprite(SpriteManager::current()->create(path)); +} diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index 94ab07546a..e4d27d09f6 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -44,6 +44,9 @@ class ButtonWidget : public Widget virtual bool on_mouse_button_down(const SDL_MouseButtonEvent& button) override; virtual bool on_mouse_motion(const SDL_MouseMotionEvent& motion) override; + void set_sprite(const std::string& path); + void set_sprite(SpritePtr sprite); + private: SpritePtr m_sprite; Rectf m_rect; diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index f79a8f0a2e..2b2236ac75 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -82,6 +82,8 @@ static const float CAMERA_ZOOM_FOCUS_PROGRESSION = 8.f; bool Editor::s_resaving_in_progress = false; +using InputType = EditorTilebox::InputType; + bool Editor::is_active() { @@ -179,6 +181,18 @@ Editor::Editor() : m_save_widget = save_button.get(); m_widgets.insert(m_widgets.begin() + 4, std::move(save_button)); + + auto mode_button = std::make_unique("images/engine/editor/tilemap.png", + Vector(160, 0), [this] { + auto& tilebox = m_toolbox_widget->get_tilebox(); + const auto& input_type = tilebox.get_input_type(); + if (input_type == InputType::OBJECT) + select_tilegroup(0); + else + select_objectgroup(0); + }); + + m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); } Editor::~Editor() From 4d395db4b28bede4b1ee1c0237380e5c91666ff8 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 15 Jun 2025 09:50:54 +0200 Subject: [PATCH 008/141] Add support for help texts for editor buttons --- src/editor/button_widget.cpp | 36 ++++++++++++++++++++++++++++-------- src/editor/button_widget.hpp | 4 ++++ src/editor/editor.cpp | 8 +++++++- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index f1a449408c..9a63397c8a 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -18,6 +18,8 @@ #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" +#include "supertux/resources.hpp" +#include "util/log.hpp" #include "video/viewport.hpp" #include "video/video_system.hpp" @@ -28,7 +30,9 @@ ButtonWidget::ButtonWidget(SpritePtr sprite, const Vector& pos, static_cast(m_sprite->get_height()))), m_grab(false), m_hover(false), - m_sig_click(std::move(sig_click)) + m_sig_click(std::move(sig_click)), + m_mouse_pos(), + m_help_text() { } @@ -49,6 +53,16 @@ ButtonWidget::draw(DrawingContext& context) context.color().draw_filled_rect(m_rect, g_config->editorhovercolor, 4.0f, LAYER_GUI-5); } + + if (m_hover && !m_help_text.empty()) + { + const auto& font = Resources::console_font; + const auto text_height = font->get_text_height(m_help_text); + const auto text_width = font->get_text_width(m_help_text); + const auto text_rect = Rectf(m_mouse_pos + Vector(32, 32), m_mouse_pos + Vector(32, 32) + Vector(text_width, text_height)); + context.color().draw_filled_rect(text_rect, Color::BLACK, LAYER_GUI - 5); + context.color().draw_text(font, m_help_text, m_mouse_pos + Vector(32, 32), FontAlignment::ALIGN_LEFT, LAYER_GUI - 5); + } } void @@ -71,10 +85,10 @@ ButtonWidget::on_mouse_button_up(const SDL_MouseButtonEvent& button) { if (button.button != SDL_BUTTON_LEFT) return false; - Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); + m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); if (m_grab) { - if (m_rect.contains(mouse_pos)) { + if (m_rect.contains(m_mouse_pos)) { if (m_sig_click) { m_sig_click(); } @@ -92,9 +106,9 @@ ButtonWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) { if (button.button != SDL_BUTTON_LEFT) return false; - Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); + m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); - if (m_rect.contains(mouse_pos)) { + if (m_rect.contains(m_mouse_pos)) { m_hover = true; m_grab = true; return true; @@ -107,12 +121,12 @@ ButtonWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) bool ButtonWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) { - Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(motion.x, motion.y); + m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(motion.x, motion.y); if (m_grab) { - m_hover = m_rect.contains(mouse_pos); + m_hover = m_rect.contains(m_mouse_pos); return true; - } else if (m_rect.contains(mouse_pos)) { + } else if (m_rect.contains(m_mouse_pos)) { m_hover = true; return false; } else { @@ -133,3 +147,9 @@ ButtonWidget::set_sprite(const std::string& path) { set_sprite(SpriteManager::current()->create(path)); } + +void +ButtonWidget::set_help_text(const std::string& help_text) +{ + m_help_text = help_text; +} diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index e4d27d09f6..972b33bada 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -47,12 +47,16 @@ class ButtonWidget : public Widget void set_sprite(const std::string& path); void set_sprite(SpritePtr sprite); + void set_help_text(const std::string& help_text); + private: SpritePtr m_sprite; Rectf m_rect; bool m_grab; bool m_hover; std::function m_sig_click; + Vector m_mouse_pos; + std::string m_help_text; private: ButtonWidget(const ButtonWidget&) = delete; diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 2b2236ac75..3f17e189cb 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -163,13 +163,15 @@ Editor::Editor() : else snap_grid_size--; }); - + + grid_size_widget->set_help_text(_("Change / Toggle grid size")); m_grid_size_widget = grid_size_widget.get(); m_widgets.insert(m_widgets.begin() + 2, std::move(grid_size_widget)); auto play_button = std::make_unique("images/engine/editor/play_button.png", Vector(96, 0), [this] { m_test_request = true; }); + play_button->set_help_text(_("Test level")); m_play_widget = play_button.get(); @@ -177,6 +179,7 @@ Editor::Editor() : auto save_button = std::make_unique("images/engine/editor/save.png", Vector(128, 0), [this] { save_level(); }); + save_button->set_help_text(_("Save level")); m_save_widget = save_button.get(); @@ -191,6 +194,7 @@ Editor::Editor() : else select_objectgroup(0); }); + mode_button->set_help_text(_("Toggle between object and tile mode")); m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); } @@ -1114,8 +1118,10 @@ Editor::retoggle_undo_tracking() // Add undo/redo button widgets. auto undo_button_widget = std::make_unique("images/engine/editor/undo.png", Vector(0, 0), [this]{ undo(); }); + undo_button_widget->set_help_text(_("Undo")); auto redo_button_widget = std::make_unique("images/engine/editor/redo.png", Vector(32, 0), [this]{ redo(); }); + redo_button_widget->set_help_text(_("Redo")); m_undo_widget = undo_button_widget.get(); m_redo_widget = redo_button_widget.get(); From 7b91ace66f39b05ca4d529d05788d9b25948d390 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 15 Jun 2025 10:06:08 +0200 Subject: [PATCH 009/141] Update play and save editor icons Thanks @Frostwithasideofsalt --- data/images/engine/editor/play_button.png | Bin 1241 -> 1978 bytes data/images/engine/editor/save.png | Bin 1562 -> 1823 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/images/engine/editor/play_button.png b/data/images/engine/editor/play_button.png index 879ed678c20b35b3215abb6d2afcc01dbdd2d104..9d3bb1ee92cfb6ce94020a1e26e9c1242edc3500 100644 GIT binary patch delta 1964 zcmV;d2UGaj3AzuEB!3BTNLh0L05#MA05#MBUS;J7000MUNkloC4d&u7Gw1eQ_UgkqGtMBKqcdX%s~oMUfWs`ElS|oq(oX zE z5s~*gFfgDmzWAaZJ9g~NEnBwyxl}5R0~diw;9cOB%fVM0V~*T%>#c)7{n<9%u;I&E zsZ`WjtA8S*B9cZ?G&wvxyqJGMRdwIKeY$$}YAqIvp-?EKl}hEKTrT$k@EY(4@HOC? zE7>jWv9D689Dn?mzv_>CM7>_)rI+?{?%aoZ^5jX$(iH1lcKhwO+pSx->Y+o22FJ$6 zt~+(=lsR+eOe0B>kE_+{U>wK$fxiG#v>MI~kbeR_z$`G#C5>oa04rCneEr+sdEhHI z-f$y;HX03i?X_3gzkh$2nwkA<(<5mmN?1hBQeKV*vQckAHMweEh;J ztrFY_yt70Auueoy_4f8!kXobBm@!6T@BK9zG^*CBph{PFH*40cp;oJr&*uTCR;vsT z4^yw#0r1|lcI{eJwFy{jts5L1)LN}J3xCAG4eiIYB%E_@=HZ8b5T1GFIeq5Y=XA%8 zCv?@SRhrM`)O)Yqd+qP<*NKUVg?gWzoz*+r-RQ zdEm?v=%VY?d+*{nmRh||9LMb3xs%b65z;KfIY)O_7u&XNWBKys3zK&3+Qp$mhkpnm zpg^fuWc&8*^E(3I`RAV}gaF`y=9L5x$8qEI=?~1v*hKEs$rE96aza!=M8q1yz_r)v z`t|G6UvJ-@mAbl`Q7#vreDcYjEXxES@}9>Ye>~~!?TyZzI|sn%=qNjO?4VdIhUw|) zIbh2w;rGM}Hpgyrx;E&bi>MxHL^sRpL0FtJP{r2;o9LpYO{> zQJ44Li%6QLX$Y;Z>5?Ro5W;wtWfy^)FY5qs*m?hjWh*{MlrLb62LTa5L_~v%7-M`C zA+n&ot3CDr=1T55=V;Vs`c5BzV~GQcCjh*crCHHhhp}xqf`|}8z*vKGj(>@<51Af6 zM`X3rT1&!vN0uf9Yi-hL(lOOwv(T~^KvfA@KvfYj7;8xybECUs3BkV1ByLtXDC{40jSR7eXLS;})=8BqGu}&bDfqIB71_I6#&K-Lz@bx@VvJ zJyE_pq-ngsUA%a`Iy05TjdwZ;NJV5egn$MGncspyI}x)qA&!&KXn!=M1<&x_$&n*R z)BEn*EV*LYl)9I3X@sCOoAn7`w37ftq?yEZ7XNB$$XcTYLYAQ_;h#f8s4CW4{`A5N zAANP>Mpr6d#qz6GeF{$7Jyo^9g~i`r+6NG0#^Ppmo$88~3#uq!koj~3F}XM1Jfb&T ze?1Rx`Qgmp4!?C><$v1GQz%~fteK_>8bS^j>*#=Y?sRLHp@nNVoHiN-vNUG*uHWiy zw|$X!Pn_vlF|e*993V*&6wL#J9UZ9EYlriN@&ie;CdPDQoon0P7LaB1U$>q#ZkS^C zGMr!16|XEHj^}xE9R+9z?>A~Q6ubM#p`XA*K#W0EaXyES3V#5kNvmS!JEj=3AV@?| zRnjD;Iy;SqPy}in1po&N#nSuM+MClPA!q>20Mo8a!5E833uNqk&k&3eG$)qsXT(5xwlX=SyFwFVI*A!LNm y>X^njtaTV;2PGTzx&6(?+<9P6ho|)aiGKk>7Zi$M7p#T=0000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00009a7bBm001r{001r{0eGc9 zb^rhX2XskIMF;5y0}d$yz`SW`000CpNkl_Kn^*cZtyg@FQ^QJiuX=&fN1xEmb@f$xEgCZLB{JG+fhlPZFe{l=|HXo zjz&;kBZd?f(vn`_wOv(YC7nbFEff(69Faf)n}2|KTQY}tyvN~;FR@vk4YjOLnq6&E zpvf3^+KVX3s>CX{uCydeE@-KnhNly=NwXbCslc+_mHZC~8bOAK2AQ2!E9!9^uy0my zU%<=Fwrl*|e-(=sz!s}v(dLWVm}WYe4JV*bj7|=4z42@A4txtp0)7}D@UbN=1~JE> zQ-3pU)WHo6t`x2R+~U`kuNa^C6M<#^+st&3>8St-Ng6=C2GjuWZ&WRK_;`ZfTYunA zXQf&AElbG3#Gs#;SZP>e%H`@{tl{}F4H^l+p0#&Fcc$wzTPKNw06xf z=$4Y&^$_s-W@zcWNp(vFzTikiF3#^YZhwyt`?}?sLfrr=OamD;#-;|jdaIHjo6ecR zBQ&c5*Jp2e3<==O*Z%^(Sgm^mlz*|dg8}Z|*Kz-HijMwoqVcW@!3Ea|Px~TAq!d*^ zQ%Ey6)qpqfcrKH@kGS4&h3{(5GVSY%);Ko<7gsi=jgCFQ&nuZyP{aogu|@2~(|=dP z=ycOsUaSW}sq2KNbv0Xlz_jJtmh0PKq;H0qhp>imp$B-wRpzN%+LIT-nXlhLaU9T> z=7bQER9J=0m(PnOR~5%U=iXaC-mmUAuRD|Oj$>vL$02cI1i^(w z1jU657lYu=b^Z|-ks!E`;6enK31$(5tl}ytStN>LAg!2TBAGBVJ^kbT>Uvdm&v8-j zy?(E!lPI|GKvA!%-o5v8etmD1=VA-MVE()Ml>huUwlSZ_&Oea(c?WnkZ*{(y=dG)C zU6UlKot!+pqcQ#%I05zm2iyn#4CFuxl=B0iURiJk`~h&@2OneI27Utk2FT{m3G)GY zr@OiJt?&HcN=Q2%to(I^vU;O@<3zZ6^+}$({*}Q;xA$!& zRSm#obaY^9tWH{0B}tNV@7{evlA*B2wMR)9I8OU%pWa9Lx)nSl0ya7XZZ?0MJG#fX*XvUGcCiE2^qKyJtp>diDnZYz}jJ zDk8nrdA#@dkbDs9DqTP<05$}vxxj4hW{mY@G#WFVoRVY#GXXUW0|Rkx8HpGps@f9g zk`*@=Gq!2kcMYJxxonL#1nRm%V?{g9P}N#Q>b#YOCPAQ1mz7Y}5$_!_DqCCIh`251 z{CL%o7?(Z{7e7K1PFG;M1`t9@SxgxZ4-xC3s@W^A{Nm(^y(hN5bp1NdJ@?h+q~q~~ za#r&4%RfU^+1cIUop=5?PP5izb1V$|Rn16}j5KS}zigsr#JUpINW1?9oNqFj0%^D5?>*LBt^$A{&iU1K zLKB+$VQ^VxrisWIh=>r9jF2?hQ9Wy69}+wX|EZZ7&U-LxuSI%(u0*7~VBG)A%nau| zA}*}nU%gmUDAlG^M1<9-*g}LbOtophCbXXku!x?BtZB^}jhQVqW&f{lTk!tWd!Mx0 zos;ooTAOMPbMbQ?ae}EiGhMFkoU;&uJ^uLPQb&sq9z1AYyYclT#)xx{b!}Jy5O?N+ znPRH;+Uu`p+uPfIXJ==7dwZL1w@V0X%oln(91b}?KIZ-R-{;n?TV;$jYT5t@7Xx63 zslvQSMTB0j=U;f?g+~^dPN(E~j=Lc2&N=%1{_+qJAM0wNj>x%OtwvydmkPlS4-dI} z_b#WWr+Dv|GTUyi74joCo6YEUyA(x1S(Yl|d|lP3D(AZJ3_vyd1)PXuV{@~(dGlsx zZ*OmHKvO4cn)SIRB1F{-pDh4_AlYiQd*@uM)dCs`J#TKl`7EFsp*aIwq{oNkf%AUP z%utP}#z&P9=&44P6@tlR>W+_(xpU_Zs!E>cYaXo@qnR-pjR45=dk8+D7;FE}4{!6#vp1J5D2)UzQp8}e1)zDJyI!w1$Cl;UIYC5XH7hWTqlZdV zm5Tttu;O&GtY|Z%+vyU#Z+5Xyw>4o|6rhN3cytI*sYdFmVx)$tUf2Z~R%?u@s-hZk z&L4@F!FzvtPx}2!Y;9d4%W~2*CC^)U@32R{^v=wC$0r|u0`PEFoNlTrsS7d2YOM+_FDh| N002ovPDHLkV1jukfW-g+ literal 1562 zcmV+#2IcvQP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vJ)9smI)9s$tpzgqwR00(qQO+^Rk1_Ki(B5|)&3;+NH za7jc#R9M5sm|bjJdo0^JKChnhJOAh7eU8=X)2Bz3Qb$?GjvqhH;NT#g%v#ID#01`ZwALIrZ~%aF z=gzIQ{F;b7eBr`{awor`_x|Wc3-b9q*=)8WV5wAMbab?JeDL5w^7%a8dqm{+<@W&( z0v~k*bk4O7lu`g|IqYa6Lc6b~Ip+`&f*@G){H6f4)&O|#S6%qK+D3p{>+J(>bfN76 zc16Kvh}(bbmV(~t-Lc z*0y|a#qXL5Hgj<^u&GpvAP7j3gmgMh7>27-?)rWALj)XF!cB1IGec#QMJiDzLk=8H>}XD*;=ONm|D8K`V$4?QmMN!<|;u@Sb+>sDnzM4l$U}oUu1qddUN!_lqjszp663j9HJx*?ITktRC>D!Ah*A$A5+X835bVJF z7+5O>LXwzT(JZiv{GoT*uS@r;(U#-XR0_aI42nEsZ?#}9Bfx`*REZg zE{qp`^$8!2#mLI*@Q-&=UTUbNbpuc@`E>!k_sq=9Y#Q*HpXd1c^;xbghnzaLmoU^UR+3h;)>@%dLroN9GASP1A5omI zF*WT_>3fmb;Hr~V5%<)(AqFTE3Y!Kj6yVQia=d(IjEi3s`QhL_t>L2qMhjB$?Nemb z6Y+!oKIZ0UsW1HJ7D?stZ^nK-8an5QqNoMdR8T}Hm&;q~e}RShl2o3U9{ z$mdrb0|U1~%F2fVXFZcs3*4NWxFPn|@XYwjm)ieXzD2I zlPINl>4n4m>Ggl}>2=TU-PzlAD8V|<^vojF;`lXe?PoKWUzuFp7l3lPTmbHFZ`1(& z{rz1T8eV+Db;UxRI_dK3#xqyPW_ M07*qoM6N<$g6#?6asU7T From f0bc09e17fcc38dba43419d9af7f6eacb17fd179 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 15 Jun 2025 10:26:53 +0200 Subject: [PATCH 010/141] Change font to small font, replace grid button --- data/images/engine/editor/grid_button.png | Bin 228 -> 1446 bytes src/editor/button_widget.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/data/images/engine/editor/grid_button.png b/data/images/engine/editor/grid_button.png index 1eecfd4e12da342e495ba1b1750cc415eebd6aba..f1d1466dec235ea1517cda53708fb3fc8b229511 100644 GIT binary patch delta 1439 zcmV;Q1z`H*0j3L(8Gi-<0047(dh`GQ010qNS#tmYHPiqAHPiuKW#tF}00me{L_t(o zN39oIOk7oP?mq7B77&OEty0)XLanwW{+cv?=!cr9i9g1u38YnnY5B0-XpK#^)|!a@ zF#c#lgQhWRVxx`K)J8t4$YPCATd+W)b^%-1U04?OeP7Pp-G6iT+`V^q!8prs&vRzZ zyv`g61lZQSs~5YEA$E!5PHoxi_4xfhc}pPRmu0VvpMcwiKm0QgG+oyfMO9RUbUKrX zEw8L1oe1LD8-Gq-W;7+%K<0&G%6lj_T z25ux`G{*p?0e_lMRRwanoJcx^LcBu*rU1yi4?iQ+zO$!0H?XhI|Iue(!jaG*F)ZML zh_FoaC*K=}g9n}kL)T4Vh=W=vKI4um_PF1{qXM4alfidaOT`7+`W^gz<*>c!p@%V$|1PDqXT+(@3xGS zEMBh+_w?Rn;-e2g0E35Kg|E(!(z+4k9i}6MsMmJO&DpnJI}Dn^tzu(RYd|V0(*mdj zXm4)^o$O<31>gko*xqp)TNmO68`$@Z;oH(^F8aSg$3qbkG&D58^LC(kX~8Ez#OjU4D;z_(cOk^`;ssLG~ zXh*cP5N7t}q-t745n!JsZJA^QJ97ajtel|8+#x)3sCK3%!5ljp{c2fdo>#)zO5~-CVOJaB1w1fXamI*wao?4F`HEaQ#)fseHk6l z3H*V8sVPwl#Eiy8FwGzc#-pI8X9sNEx{a@;lzMAh8}B!_w1CIM<*ni^0C`Ngl%5$- zbbqX@EL+xJi~J395xN${D44KDLEj4@?&*oOw6R>m*H6CB+iZ-!;&?)c5{eEnx~Lt# z^fQkFo`@f&@x-W=+`RWop*qji;a6VifM8PF{ z6zIB<7sbYs&XQCpx7XAxy1qv;wpUmZGTDqxbB2&!%Rb<6dlZMtF+lybWYQ)GEq_ZS z6092I3@Iz-vcj_Cl_x*+!?`hIC?RUg3iuP2nO${>gf_1x1q5S@EuUKCcq~EWHTAaAl8DV z=pxKaPg|ql!gr&8)vJhDg@aYk4S%YRl2`!|QWf;{b1&Mwy|N-=^u>yZn;?-`E?;3~ z@yRR<#Y6dMBl!o5j(YVV+w4YjM&t3Ttj`7b;@r2A$7Qp#9~UwdyxZBHHT7-^j1>Y3 z{lTe^uZaSf&#{YRKkfSF`~`3jq8{Cv^D16E#F*%kwoZR~=7-CZQxBCk4S#v?c{GF7 zX+I8(><=A<$G;zk`}W)m^9yruWpWDK9xs1}QK+S{q@KDu4b-Nmmq9;6hj=_@uBQ+q zhc&QNmoSAd%+JFwzy6+%Mq~cZ&wd@bI5x4T#GIaHGc_Nl^^Q)LiknqQ6x%`?q{k}> tBgVWy&-yrJ6PtOS*AuSFycq3y{x4C>fTyikkL>^e002ovPDHLkV1jBRw9o(m delta 211 zcmZ3+{e*FXWIYoD1H&JQw~K)kV{wqX6XVU3I`u#fOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>4nJ=qZCRW5rVY zGN2%PiKnkC`%5-vJ`EM_Rd=rfg{(YXd>lhOjwdHbylN11`uqRCe7B6U^3x!nE#D5V zU{yZA)ONAQY}Q3LU5T?9izRcfG=aHoZYsget_text_height(m_help_text); const auto text_width = font->get_text_width(m_help_text); const auto text_rect = Rectf(m_mouse_pos + Vector(32, 32), m_mouse_pos + Vector(32, 32) + Vector(text_width, text_height)); From ddfb8c9fd7cbf4431282475e2d5d0398bb719db7 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 15 Jun 2025 11:16:18 +0200 Subject: [PATCH 011/141] Make button icons smaller, add function to ToolIcon class for setting the mode --- src/editor/button_widget.cpp | 2 +- src/editor/editor.cpp | 18 ++++++++++++++++++ src/editor/tool_icon.cpp | 12 ++++++++++-- src/editor/tool_icon.hpp | 1 + 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index cf04c616bc..b82aa7ff70 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -43,7 +43,7 @@ ButtonWidget::draw(DrawingContext& context) LAYER_GUI-5); if (m_sprite) { - m_sprite->draw(context.color(), m_rect.p1(), LAYER_GUI-5); + m_sprite->draw_scaled(context.color(), m_rect.grown(-4.0f), LAYER_GUI - 5); } if (m_grab) { diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 3f17e189cb..26bf661c4c 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -197,6 +197,24 @@ Editor::Editor() : mode_button->set_help_text(_("Toggle between object and tile mode")); m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); + + auto select_mode_mouse_button = std::make_unique( + "images/engine/editor/select-mode0.png", Vector(192, 0), [] {}); + select_mode_mouse_button->set_help_text(_("Draw mode (The current tool applies to the tile under the mouse)")); + auto select_mode_area_button = std::make_unique( + "images/engine/editor/select-mode1.png", Vector(224, 0), [] {}); + select_mode_area_button->set_help_text(_("Box draw mode (The current tool applies to an area / box drawn with the mouse)")); + auto select_mode_fill_button = std::make_unique( + "images/engine/editor/select-mode2.png", Vector(256, 0), [] {}); + select_mode_fill_button->set_help_text(_("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")); + auto select_mode_same_button = std::make_unique( + "images/engine/editor/select-mode3.png", Vector(288, 0), [] {}); + select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); + + m_widgets.insert(m_widgets.begin() + 6, std::move(select_mode_mouse_button)); + m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_area_button)); + m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_fill_button)); + m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_same_button)); } Editor::~Editor() diff --git a/src/editor/tool_icon.cpp b/src/editor/tool_icon.cpp index 01ea1e79e3..a8482f1ab5 100644 --- a/src/editor/tool_icon.cpp +++ b/src/editor/tool_icon.cpp @@ -45,8 +45,16 @@ ToolIcon::draw(DrawingContext& context) void ToolIcon::next_mode() { - m_mode++; - if (m_mode >= m_surf_count) { + set_mode(m_mode + 1); +} + +void +ToolIcon::set_mode(int mode) +{ + m_mode = mode; + + if (m_mode >= m_surf_count) + { m_mode = 0; } } diff --git a/src/editor/tool_icon.hpp b/src/editor/tool_icon.hpp index 911ed8c5af..d94efc8963 100644 --- a/src/editor/tool_icon.hpp +++ b/src/editor/tool_icon.hpp @@ -32,6 +32,7 @@ class ToolIcon final void draw(DrawingContext& context); inline int get_mode() const { return m_mode; } + void set_mode(int mode); void next_mode(); From ae1f3baebd279f887810b0528d29dd237fb8326c Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 15 Jun 2025 11:59:56 +0200 Subject: [PATCH 012/141] Make mode buttons actually work --- src/editor/editor.cpp | 16 ++++++++++++---- src/editor/toolbox_widget.cpp | 7 +++++++ src/editor/toolbox_widget.hpp | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 26bf661c4c..01ff1a044b 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -199,16 +199,24 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); auto select_mode_mouse_button = std::make_unique( - "images/engine/editor/select-mode0.png", Vector(192, 0), [] {}); + "images/engine/editor/select-mode0.png", Vector(192, 0), [this] { + m_toolbox_widget->set_tileselect_select_mode(0); + }); select_mode_mouse_button->set_help_text(_("Draw mode (The current tool applies to the tile under the mouse)")); auto select_mode_area_button = std::make_unique( - "images/engine/editor/select-mode1.png", Vector(224, 0), [] {}); + "images/engine/editor/select-mode1.png", Vector(224, 0), [this] { + m_toolbox_widget->set_tileselect_select_mode(1); + }); select_mode_area_button->set_help_text(_("Box draw mode (The current tool applies to an area / box drawn with the mouse)")); auto select_mode_fill_button = std::make_unique( - "images/engine/editor/select-mode2.png", Vector(256, 0), [] {}); + "images/engine/editor/select-mode2.png", Vector(256, 0), [this] { + m_toolbox_widget->set_tileselect_select_mode(2); + }); select_mode_fill_button->set_help_text(_("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")); auto select_mode_same_button = std::make_unique( - "images/engine/editor/select-mode3.png", Vector(288, 0), [] {}); + "images/engine/editor/select-mode3.png", Vector(288, 0), [this] { + m_toolbox_widget->set_tileselect_select_mode(3); + }); select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); m_widgets.insert(m_widgets.begin() + 6, std::move(select_mode_mouse_button)); diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index 8565f6c845..f0b58c7005 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -300,6 +300,13 @@ EditorToolboxWidget::get_tileselect_select_mode() const return m_select_mode->get_mode(); } +void +EditorToolboxWidget::set_tileselect_select_mode(int mode) +{ + m_select_mode->set_mode(mode); + update_mouse_icon(); +} + int EditorToolboxWidget::get_tileselect_move_mode() const { diff --git a/src/editor/toolbox_widget.hpp b/src/editor/toolbox_widget.hpp index f337191fcf..f5bf17c5b3 100644 --- a/src/editor/toolbox_widget.hpp +++ b/src/editor/toolbox_widget.hpp @@ -60,6 +60,8 @@ class EditorToolboxWidget final : public Widget void update_mouse_icon(); + void set_tileselect_select_mode(int mode); + inline EditorTilebox& get_tilebox() const { return *m_tilebox; } inline bool has_mouse_focus() const { return m_has_mouse_focus; } From ba7b3571ccffd43e24e0257b3d0d10fba4d29d25 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Fri, 20 Jun 2025 22:39:56 +0200 Subject: [PATCH 013/141] Change icon for toggling between tile and object mode. Thanks @Frostwithasideofsalt --- .../engine/editor/toggle_tile_object_mode.png | Bin 0 -> 2334 bytes src/editor/editor.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 data/images/engine/editor/toggle_tile_object_mode.png diff --git a/data/images/engine/editor/toggle_tile_object_mode.png b/data/images/engine/editor/toggle_tile_object_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae0f7cf4c9474f09e6762cc13c8834baaece132 GIT binary patch literal 2334 zcmV+(3E}pMP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vGf6951U69E94oEQKA00(qQO+^Rk1{4$~E~$f1#Q*>Z zbV)=(R9M69S6ggc)fHX)+{e6U>>1B^z+=ZwsKH5`7?KofVn`D=Eec9S01*NSZJ|VI zD_THpkw9vy22=@^d;p@f>PH2oFZHLCQfMQR0CAj$6Vk+wJRHB@o|orw@65et_rs23 zhdA_upGw`*`M6u>?!C^^+Gp!iTY`m>q#he&jU^; zM5P2N1ldpE44`gIj+h&+17Jq6k4CV8KTj|oD62c6-7B?FCkGZWqjb*l4>9ze40XMP zzP)~d7z#j&syglxLi;Iz69B&X-=$Ypcf#>Kxv1Xe{U0&^^(!x$|9|Os=J3vA`~uo6es* zi?Oj${@{au(b(7+4-XAe*V!&E7K??MvL2geMhIppF9C4QF!pI<24I6wgpdX_D?RPT zCCHf`EmHqhT!w|n=q_X7CsY;9w{ z`og3%eM0a?HSr*XEl8D95MTfb7%4W@x4@zucig!iLJ0cs-~kQ>gGi^-h{a;aWTs#k z2A+NPC%mEgtK{*_>W&5n2e@hNnl6BD2RMGUW;D+Bhu1T(%e3G92B55Q>Jcnoz5+%e zE{BJEtf}}UxgBoxNb4aA1Om)#Q(0LM)zwu{RTYs)84`&E@`eGA$3ru!CIG0Yh(S@5 zsBKG0B#2q^0^Ja=0r)3icWt?^S=@2wdISOi@!-~bGu73Lf*y~Dgb>g)4asDZ{eC|Q zAujh)mkXy)pTYwVYy$w2Qetv)0=>OGL4I!P%xlgW@}*;addhmy@^r=*mM z%jNP0N&=~Pe6qwa@_72`A0Za2!lhnu{P$ z1YVD)I5;@u$Ye5HC=}p!yQN_m_R5tF%JVP0SW;eI4wuUf03|ziY|UR^QQ>ZAxMfy^ z01ysG5ROD#6XO$Jo+e;H01Z`@i?Da^i)cN32)(_%P*qKJxm*Z`!{qaMp{go+dU|}T zR;@vGbnf8BQd52^5>RYk*r5DlL+k=Z2yK(&35ga{s1np-|Ls1m(^yG96 z0D(YB&CmAy9Jk%pIO}sb95gaA%+Y9cu8%)Cc`|95X36;Y7|x$R$0tv;aeMn2%I324 zon4RNrn)+=jK%2do9{+dWd&MWkD#{prsktZ5AR@ALj}{EM_)(B8A!=&+cw#@ zjeI`Oxm*s}Y{qx@rmr9v4B`v5H&SI~6*V?~5vkN9YHMrJ)YLQ^XWhDWsI9$`-gxuP zCy2xzW@%gvz>-udIs0K*7Wek{(2>@|=+yI~p-`DHO%tYRAQUP?eSJM_Df#G; z!xRh#XE*2bdE9*SD&DxUd0s3jC0>91b=-5$7S=Qex1DICwzf7T6N#~8GSLFyVP=`V z3Ik+%y*_K{(mK7SrW!Rhi{bbC008lLoId{O;5>CZ91a|6`529jx3SmjrBo_~k&#jE z>Fq;4Z{n3#UY(kpoOlDkdjMLP<-}(^Ot`43>Vd_JYnoTASTXNZ!C(-6zaOS)Lf3T^ z3dQNRn@aJkuf8^_s%j}Sakv{FI?z8Af=rl67SEQZ2o8MKkg+BgyLM("images/engine/editor/tilemap.png", + auto mode_button = std::make_unique("images/engine/editor/toggle_tile_object_mode.png", Vector(160, 0), [this] { auto& tilebox = m_toolbox_widget->get_tilebox(); const auto& input_type = tilebox.get_input_type(); From a23e3edf88344deae7a818cc2c394390622a833b Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 21 Jun 2025 08:23:06 +0200 Subject: [PATCH 014/141] WIP: Add editor controls for objects on mouse click --- src/editor/editor.cpp | 36 +++++++++++++++++++++++++++++++++++ src/editor/editor.hpp | 10 +++++++++- src/editor/overlay_widget.cpp | 24 +++++++++++++++++++++++ src/object/tilemap.cpp | 2 +- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index af5248679e..f936e66e01 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -120,6 +120,7 @@ Editor::Editor() : m_tileset(nullptr), m_has_deprecated_tiles(false), m_widgets(), + m_controls(), m_undo_widget(), m_redo_widget(), m_grid_size_widget(), @@ -245,6 +246,14 @@ Editor::draw(Compositor& compositor) widget->draw(context); } + context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), + Color(0.2f, 0.2f, 0.2f), LAYER_GUI); + + for(const auto& control : m_controls) + { + control->draw(context); + } + // If camera scale must be changed, change it here. if (m_new_scale != 0.f) { @@ -383,6 +392,11 @@ Editor::update(float dt_sec, const Controller& controller) widget->update(dt_sec); } + for(const auto& control : m_controls) + { + control->update(dt_sec); + } + // Now that all widgets have been updated, which should have relinquished // pointers to objects marked for deletion, we can actually delete them. for (auto& sector : m_level->get_sectors()) @@ -1272,3 +1286,25 @@ Editor::pack_addon() *zip.Add_File(id + ".nfo") << ss.rdbuf(); } + +void +Editor::addControl(const std::string& name, std::unique_ptr new_control) +{ + float height = 35.f; + for (const auto& control : m_controls) { + height = std::max(height, control->get_rect().get_bottom() + 5.f); + } + + if (new_control.get()->get_rect().get_width() == 0.f || new_control.get()->get_rect().get_height() == 0.f) { + new_control.get()->set_rect(Rectf(100.f, height, 200.f - 1.0f, height + 20.f)); + } else { + new_control.get()->set_rect(Rectf(new_control.get()->get_rect().get_left(), + height, + new_control.get()->get_rect().get_right(), + height + new_control.get()->get_rect().get_height())); + } + + new_control.get()->m_label = std::make_unique(Rectf(3.f, height, 100.f, height + 20.f), std::move(name)); + //new_control.get()->m_on_change = std::function([this](){ this->push_version(); }); + m_controls.push_back(std::move(new_control)); +} diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index a2a30cac44..b2954943c0 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -27,6 +27,7 @@ #include "editor/toolbox_widget.hpp" #include "editor/layers_widget.hpp" #include "editor/scroller_widget.hpp" +#include "interface/control.hpp" #include "supertux/screen.hpp" #include "supertux/world.hpp" #include "util/currenton.hpp" @@ -80,6 +81,11 @@ class Editor final : public Screen, void event(const SDL_Event& ev) override; void on_window_resize() override; + std::vector>& get_controls() + { + return m_controls; + } + void disable_keyboard() { m_enabled = false; } inline Level* get_level() const { return m_level.get(); } @@ -161,6 +167,7 @@ class Editor final : public Screen, void queue_layers_refresh(); + void addControl(const std::string& name, std::unique_ptr new_control); void retoggle_undo_tracking(); void undo_stack_cleanup(); @@ -185,7 +192,6 @@ class Editor final : public Screen, void save_level(const std::string& filename = "", bool switch_file = false); void test_level(const std::optional>& test_pos); void update_keyboard(const Controller& controller); - void keep_camera_in_bounds(); protected: @@ -223,6 +229,8 @@ class Editor final : public Screen, bool m_has_deprecated_tiles; std::vector > m_widgets; + std::vector> m_controls; + ButtonWidget* m_undo_widget; ButtonWidget* m_redo_widget; ButtonWidget* m_grid_size_widget; diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index a845833127..c1ff85dd67 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -22,10 +22,12 @@ #include "editor/node_marker.hpp" #include "editor/object_menu.hpp" #include "editor/object_info.hpp" +#include "editor/object_option.hpp" #include "editor/tile_selection.hpp" #include "editor/tip.hpp" #include "gui/menu.hpp" #include "gui/menu_manager.hpp" +#include "interface/control_textbox.hpp" #include "math/bezier.hpp" #include "object/camera.hpp" #include "object/path_gameobject.hpp" @@ -878,6 +880,28 @@ EditorOverlayWidget::process_left_click() case EditorTilebox::InputType::NONE: case EditorTilebox::InputType::OBJECT: + + if (m_hovered_object.get() != nullptr) + { + // WIP: + auto& controls = m_editor.get_controls(); + controls.clear(); + ObjectSettings os = m_hovered_object.get()->get_settings(); + for(const auto& option : os.get_options()) + { + if (dynamic_cast(option.get())) + { + m_editor.addControl(option.get()->get_text(), nullptr); + } + else + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->put_text(option.get()->to_string()); + m_editor.addControl(option.get()->get_text(), std::move(textbox)); + } + } + } switch (m_editor.get_tileselect_move_mode()) { case 0: diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index 9e4faeec6e..108e0e4e1d 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -689,7 +689,7 @@ TileMap::get_offset() const // Apply custom offset for tilemap to fit buttons at the top of the editor. // This extra offset isn't saved. if (Editor::is_active() && !Level::current()->is_saving_in_progress()) - return m_offset + Vector(0, 32); + return m_offset + Vector(200, 32); return m_offset; } From 187a7553d5aaa7d33e499b279af1b82afb812b2c Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 21 Jun 2025 08:42:08 +0200 Subject: [PATCH 015/141] Change layer for properties panel background, add background for toolbar --- src/editor/editor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index f936e66e01..0f8e1ef1be 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -246,8 +246,11 @@ Editor::draw(Compositor& compositor) widget->draw(context); } + context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), + Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 6); + context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), - Color(0.2f, 0.2f, 0.2f), LAYER_GUI); + Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 1); for(const auto& control : m_controls) { From 2d5e37c9261a50797653df468214dd46f0002175 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 21 Jun 2025 13:40:47 +0200 Subject: [PATCH 016/141] Add more edit capabilities to sidebar --- src/editor/editor.cpp | 6 +++++- src/editor/overlay_widget.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 0f8e1ef1be..3fa1246f92 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -250,7 +250,7 @@ Editor::draw(Compositor& compositor) Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 6); context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), - Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 1); + Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 6); for(const auto& control : m_controls) { @@ -974,6 +974,10 @@ Editor::event(const SDL_Event& ev) { if (!m_enabled || !m_levelloaded) return; + for(const auto& control : m_controls) + if (control->event(ev)) + return; + try { if (ev.type == SDL_KEYDOWN) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index c1ff85dd67..8cbcdaa532 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -27,7 +27,11 @@ #include "editor/tip.hpp" #include "gui/menu.hpp" #include "gui/menu_manager.hpp" +#include "interface/control_checkbox.hpp" +#include "interface/control_enum.hpp" #include "interface/control_textbox.hpp" +#include "interface/control_textbox_float.hpp" +#include "interface/control_textbox_int.hpp" #include "math/bezier.hpp" #include "object/camera.hpp" #include "object/path_gameobject.hpp" @@ -893,6 +897,31 @@ EditorOverlayWidget::process_left_click() { m_editor.addControl(option.get()->get_text(), nullptr); } + else if (auto int_option = dynamic_cast(option.get())) + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->bind_value(int_option->get_value()); + m_editor.addControl(option.get()->get_text(), std::move(textbox)); + } + else if (auto float_option = dynamic_cast(option.get())) + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->bind_value(float_option->get_value()); + m_editor.addControl(option.get()->get_text(), std::move(textbox)); + } + else if (auto bool_option = dynamic_cast(option.get())) + { + auto checkbox = std::make_unique(); + checkbox.get()->set_rect(Rectf(0, 32, 20, 32)); + checkbox.get()->bind_value(bool_option->get_value()); + m_editor.addControl(option.get()->get_text(), std::move(checkbox)); + } + // else if (auto enum_option = dynamic_cast(option.get())) + // { + // auto dropdown = std::make_unique(); + // } else { auto textbox = std::make_unique(); From 553df0e1e329e399ec3d5c7b3dd4135a086106c8 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 23 Jun 2025 23:31:27 +0200 Subject: [PATCH 017/141] Add "edit script" button for script properties --- src/editor/overlay_widget.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 8cbcdaa532..cf70b44bbd 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -26,7 +26,9 @@ #include "editor/tile_selection.hpp" #include "editor/tip.hpp" #include "gui/menu.hpp" +#include "gui/menu_script.hpp" #include "gui/menu_manager.hpp" +#include "interface/control_button.hpp" #include "interface/control_checkbox.hpp" #include "interface/control_enum.hpp" #include "interface/control_textbox.hpp" @@ -918,6 +920,15 @@ EditorOverlayWidget::process_left_click() checkbox.get()->bind_value(bool_option->get_value()); m_editor.addControl(option.get()->get_text(), std::move(checkbox)); } + else if (auto script_option = dynamic_cast(option.get())) + { + auto button = std::make_unique(_("Edit script")); + const auto value_ptr = script_option->get_value(); + button.get()->m_on_change = std::function([value_ptr]() { + MenuManager::instance().push_menu(std::make_unique(value_ptr)); + }); + m_editor.addControl(option.get()->get_text(), std::move(button)); + } // else if (auto enum_option = dynamic_cast(option.get())) // { // auto dropdown = std::make_unique(); From 583e027758485ac56780e0dbe82e695cbe1878a1 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 02:25:11 +0200 Subject: [PATCH 018/141] Move positioning code over to overlay_widget and editor --- src/editor/editor.cpp | 2 +- src/editor/overlay_widget.cpp | 5 +++-- src/object/tilemap.cpp | 5 ----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 3fa1246f92..6049a958bd 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -517,7 +517,7 @@ void Editor::keep_camera_in_bounds() { Camera& camera = m_sector->get_camera(); - camera.keep_in_bounds(Rectf(0.f, 0.f, + camera.keep_in_bounds(Rectf(-200.f, -32.0f, std::max(0.0f, m_sector->get_editor_width() + 128.f / camera.get_current_scale()), std::max(0.0f, m_sector->get_editor_height() + 32.f / camera.get_current_scale()))); diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index cf70b44bbd..723c595e25 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -1272,8 +1272,9 @@ EditorOverlayWidget::update_pos() if(m_editor.get_sector() == nullptr) return; m_sector_pos = m_mouse_pos / m_editor.get_sector()->get_camera().get_current_scale() + - m_editor.get_sector()->get_camera().get_translation(); - m_hovered_tile = sp_to_tp(m_sector_pos); + m_editor.get_sector()->get_camera().get_translation() + Vector(200.f, 32.f); + + m_hovered_tile = sp_to_tp(m_sector_pos - Vector(200.f, 32.f)); if (m_last_hovered_tile != m_hovered_tile) { diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index 108e0e4e1d..a53dde685d 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -686,11 +686,6 @@ TileMap::resize(const Size& newsize, const Size& resize_offset) { Vector TileMap::get_offset() const { - // Apply custom offset for tilemap to fit buttons at the top of the editor. - // This extra offset isn't saved. - if (Editor::is_active() && !Level::current()->is_saving_in_progress()) - return m_offset + Vector(200, 32); - return m_offset; } From 22544508701259ab7441027fc33f17728f03817a Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 08:51:27 +0200 Subject: [PATCH 019/141] Fix screen coordinates for new positions --- src/editor/overlay_widget.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 723c595e25..6a99e2c5eb 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -173,7 +173,8 @@ EditorOverlayWidget::drag_rect() const end_y = m_drag_start.y; } - return Rectf(start_x, start_y, end_x, end_y); + return Rectf(start_x, start_y, end_x, end_y) + .moved(Vector(-200.0f, -32.0f)); } void @@ -523,7 +524,7 @@ EditorOverlayWidget::hover_object() for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) { const Rectf& bbox = moving_object.get_bbox(); - if (bbox.contains(m_sector_pos)) + if (bbox.contains(m_sector_pos - Vector(200.f, 32.f))) { if (&moving_object != m_hovered_object) { @@ -922,11 +923,12 @@ EditorOverlayWidget::process_left_click() } else if (auto script_option = dynamic_cast(option.get())) { - auto button = std::make_unique(_("Edit script")); + auto button = std::make_unique(_("Edit...")); const auto value_ptr = script_option->get_value(); button.get()->m_on_change = std::function([value_ptr]() { MenuManager::instance().push_menu(std::make_unique(value_ptr)); }); + button.get()->set_rect(Rectf(0, 32, 20, 32)); m_editor.addControl(option.get()->get_text(), std::move(button)); } // else if (auto enum_option = dynamic_cast(option.get())) From 3d8e69a53361f89734211a1710480db2b322c04e Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 08:59:53 +0200 Subject: [PATCH 020/141] Fix initial camera position --- src/object/camera.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/object/camera.cpp b/src/object/camera.cpp index 55adec3969..ce75ff30bd 100644 --- a/src/object/camera.cpp +++ b/src/object/camera.cpp @@ -191,6 +191,8 @@ Camera::after_editor_set() init_path_pos(Vector(0,0)); } } + + m_translation -= Vector(200.f, 32.f); } void From 382151267d9ea5d01654b61acdccbfdff11cd598 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 10:01:32 +0200 Subject: [PATCH 021/141] Label and control classes: code style --- src/interface/control.hpp | 18 ++++++++++++++++-- src/interface/label.cpp | 31 +++++++++++++------------------ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/interface/control.hpp b/src/interface/control.hpp index 9a79f03402..6c9a7f5129 100644 --- a/src/interface/control.hpp +++ b/src/interface/control.hpp @@ -30,8 +30,22 @@ class InterfaceControl : public Widget InterfaceControl(); ~InterfaceControl() override {} - virtual void draw(DrawingContext& context) override { if (m_label) m_label->draw(context); } - virtual bool on_mouse_motion(const SDL_MouseMotionEvent& motion) override { if (m_label) m_label->on_mouse_motion(motion); return false; } + virtual void draw(DrawingContext& context) override + { + if (m_label) + { + m_label->draw(context); + } + } + + virtual bool on_mouse_motion(const SDL_MouseMotionEvent& motion) override + { + if (m_label) + { + m_label->on_mouse_motion(motion); + } + return false; + } inline void set_focus(bool focus) { m_has_focus = focus; } inline bool has_focus() const { return m_has_focus; } diff --git a/src/interface/label.cpp b/src/interface/label.cpp index 6f5ae038e8..55677d8995 100644 --- a/src/interface/label.cpp +++ b/src/interface/label.cpp @@ -54,25 +54,20 @@ InterfaceLabel::draw(DrawingContext& context) Color::WHITE); if (!fits(m_label) && m_rect.contains(m_mouse_pos)) { - context.color().draw_filled_rect(Rectf(m_mouse_pos, m_mouse_pos + Vector( - Resources::control_font - ->get_text_width(m_label), - Resources::control_font->get_height())) - .grown(5.f).moved(Vector(0, 32)), - Color(0.1f, 0.1f, 0.1f, 0.8f), - LAYER_GUI + 10); - context.color().draw_filled_rect(Rectf(m_mouse_pos, m_mouse_pos + Vector( - Resources::control_font - ->get_text_width(m_label), - Resources::control_font->get_height())) - .grown(3.f).moved(Vector(0, 32)), - Color(1.f, 1.f, 1.f, 0.1f), - LAYER_GUI + 10); - context.color().draw_text(Resources::control_font, - m_label, + auto text_width = Resources::control_font->get_text_width(m_label); + auto text_height = Resources::control_font->get_height(); + auto base_rect = Rectf(m_mouse_pos, m_mouse_pos + Vector(text_width, text_height)); + auto box_layer = LAYER_GUI + 10; + + context.color().draw_filled_rect(base_rect.grown(5.f).moved(Vector(0, 32)), + Color(0.1f, 0.1f, 0.1f, 0.8f), box_layer); + + context.color().draw_filled_rect(base_rect.grown(3.f).moved(Vector(0, 32)), + Color(1.f, 1.f, 1.f, 0.1f), box_layer); + + context.color().draw_text(Resources::control_font, m_label, m_mouse_pos + Vector(0, 33.f), - FontAlignment::ALIGN_LEFT, - LAYER_GUI + 11, + FontAlignment::ALIGN_LEFT, LAYER_GUI + 11, Color::WHITE); } } From 599d6726afa838017dcb765fdd797a247e1af9de Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 11:19:43 +0200 Subject: [PATCH 022/141] Base work to support adding descriptions to editor properties to be shown on mouse over --- src/editor/editor.cpp | 23 ++++++++++++++--------- src/editor/editor.hpp | 2 +- src/editor/object_option.hpp | 2 ++ src/interface/label.cpp | 35 +++++++++++++++++++++++++++++++---- src/interface/label.hpp | 6 ++++++ 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 6049a958bd..52ab0be74e 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1295,23 +1295,28 @@ Editor::pack_addon() } void -Editor::addControl(const std::string& name, std::unique_ptr new_control) +Editor::addControl(const std::string& name, std::unique_ptr new_control, const std::string& description) { float height = 35.f; for (const auto& control : m_controls) { height = std::max(height, control->get_rect().get_bottom() + 5.f); } - if (new_control.get()->get_rect().get_width() == 0.f || new_control.get()->get_rect().get_height() == 0.f) { - new_control.get()->set_rect(Rectf(100.f, height, 200.f - 1.0f, height + 20.f)); - } else { - new_control.get()->set_rect(Rectf(new_control.get()->get_rect().get_left(), - height, - new_control.get()->get_rect().get_right(), - height + new_control.get()->get_rect().get_height())); + auto control_rect = new_control.get()->get_rect(); + Rectf target_rect = Rectf(); + if (control_rect.get_width() == 0.f || control_rect.get_height() == 0.f) + { + target_rect = Rectf(100.f, height, 200.f - 1.0f, height + 20.f); + } + else + { + target_rect = Rectf(control_rect.get_left(), height, + control_rect.get_right(), height + control_rect.get_height()); } + new_control.get()->set_rect(target_rect); - new_control.get()->m_label = std::make_unique(Rectf(3.f, height, 100.f, height + 20.f), std::move(name)); + auto dimensions = Rectf(3.f, height, 100.f, height + 20.f); + new_control.get()->m_label = std::make_unique(dimensions, std::move(name), std::move(description)); //new_control.get()->m_on_change = std::function([this](){ this->push_version(); }); m_controls.push_back(std::move(new_control)); } diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index b2954943c0..9c976d5947 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -167,7 +167,7 @@ class Editor final : public Screen, void queue_layers_refresh(); - void addControl(const std::string& name, std::unique_ptr new_control); + void addControl(const std::string& name, std::unique_ptr new_control, const std::string& description = ""); void retoggle_undo_tracking(); void undo_stack_cleanup(); diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index d103c42fa3..4f37cd91b9 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -73,10 +73,12 @@ class BaseObjectOption inline const std::string& get_key() const { return m_key; } inline const std::string& get_text() const { return m_text; } + inline const std::string& get_description() const { return m_description; } inline unsigned int get_flags() const { return m_flags; } protected: const std::string m_text; + const std::string m_description; const std::string m_key; const unsigned int m_flags; diff --git a/src/interface/label.cpp b/src/interface/label.cpp index 55677d8995..04877ea877 100644 --- a/src/interface/label.cpp +++ b/src/interface/label.cpp @@ -23,6 +23,7 @@ InterfaceLabel::InterfaceLabel() : m_rect(), m_label(), + m_description(), m_mouse_pos(0.0f, 0.0f) { } @@ -30,6 +31,15 @@ InterfaceLabel::InterfaceLabel() : InterfaceLabel::InterfaceLabel(const Rectf& rect, std::string label) : m_rect(rect), m_label(std::move(label)), + m_description(), + m_mouse_pos(0.0f, 0.0f) +{ +} + +InterfaceLabel::InterfaceLabel(const Rectf& rect, std::string label, std::string description) : + m_rect(rect), + m_label(std::move(label)), + m_description(std::move(description)), m_mouse_pos(0.0f, 0.0f) { } @@ -53,9 +63,19 @@ InterfaceLabel::draw(DrawingContext& context) LAYER_GUI, Color::WHITE); - if (!fits(m_label) && m_rect.contains(m_mouse_pos)) { - auto text_width = Resources::control_font->get_text_width(m_label); - auto text_height = Resources::control_font->get_height(); + auto has_description = m_description.length() > 0; + if ((!fits(m_label) || has_description) && m_rect.contains(m_mouse_pos)) { + auto font = Resources::control_font; + auto text_width = font->get_text_width(m_label); + auto text_height = font->get_height() * (has_description ? 2 : 1); + + if (has_description) + { + auto description_width = font->get_text_width(m_description); + if (description_width > text_width) + text_width = description_width; + } + auto base_rect = Rectf(m_mouse_pos, m_mouse_pos + Vector(text_width, text_height)); auto box_layer = LAYER_GUI + 10; @@ -65,10 +85,17 @@ InterfaceLabel::draw(DrawingContext& context) context.color().draw_filled_rect(base_rect.grown(3.f).moved(Vector(0, 32)), Color(1.f, 1.f, 1.f, 0.1f), box_layer); - context.color().draw_text(Resources::control_font, m_label, + context.color().draw_text(font, m_label, m_mouse_pos + Vector(0, 33.f), FontAlignment::ALIGN_LEFT, LAYER_GUI + 11, Color::WHITE); + if (has_description) + { + context.color().draw_text(font, m_description, + m_mouse_pos + Vector(0, 33.f + font->get_height() + 2.5f), + FontAlignment::ALIGN_LEFT, LAYER_GUI + 11, + Color::YELLOW); + } } } diff --git a/src/interface/label.hpp b/src/interface/label.hpp index e8c5133a13..b7815d26a7 100644 --- a/src/interface/label.hpp +++ b/src/interface/label.hpp @@ -26,6 +26,7 @@ class InterfaceLabel : public Widget public: InterfaceLabel(); InterfaceLabel(const Rectf& rect, std::string label); + InterfaceLabel(const Rectf& rect, std::string label, std::string description); ~InterfaceLabel() override {} virtual void draw(DrawingContext& context) override; @@ -37,6 +38,9 @@ class InterfaceLabel : public Widget inline void set_label(const std::string& label) { m_label = label; } inline const std::string& get_label() const { return m_label; } + inline void set_description(const std::string& description) { m_description = description; } + inline const std::string& get_description() const { return m_description; } + bool fits(const std::string& text) const; std::string get_truncated_text() const; @@ -45,6 +49,8 @@ class InterfaceLabel : public Widget Rectf m_rect; /** The text of the label */ std::string m_label; + /** Some descriptive text for the label */ + std::string m_description; private: Vector m_mouse_pos; From 86844e06772d2824102ac7c83eee65ca5f4c8c8a Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 12:16:31 +0200 Subject: [PATCH 023/141] Added example descriptions per data type --- src/editor/object_option.cpp | 1 + src/editor/object_option.hpp | 4 +++- src/editor/object_settings.cpp | 28 +++++++++++++++++++++------- src/editor/overlay_widget.cpp | 16 +++++++++------- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 14873aa1ec..0d630337be 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -54,6 +54,7 @@ bool BaseObjectOption::s_allow_saving_defaults = false; BaseObjectOption::BaseObjectOption(const std::string& text, const std::string& key, unsigned int flags) : m_text(text), + m_description(), m_key(key), m_flags(flags), m_last_state() diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index 4f37cd91b9..56ab236dd5 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -74,11 +74,13 @@ class BaseObjectOption inline const std::string& get_key() const { return m_key; } inline const std::string& get_text() const { return m_text; } inline const std::string& get_description() const { return m_description; } + void set_description(const std::string& description) { m_description = description; } + inline unsigned int get_flags() const { return m_flags; } protected: const std::string m_text; - const std::string m_description; + std::string m_description; const std::string m_key; const unsigned int m_flags; diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index 3215d3fea0..e620c3812f 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -56,7 +56,9 @@ ObjectSettings::add_objects(const std::string& text, std::vector)>& add_object_func, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, get_objects_param, add_object_func, key, flags)); + auto select_option = std::make_unique(text, value_ptr, get_objects_param, add_object_func, key, flags); + select_option->set_description("Select option (should be described in more detail here)"); + add_option(std::move(select_option)); } void @@ -65,7 +67,9 @@ ObjectSettings::add_color(const std::string& text, Color* value_ptr, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, true, flags)); + auto color_option = std::make_unique(text, value_ptr, key, default_value, true, flags); + color_option->set_description("Color option (should be described in more detail here)"); + add_option(std::move(color_option)); } void @@ -74,7 +78,9 @@ ObjectSettings::add_rgba(const std::string& text, Color* value_ptr, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, true, flags)); + auto color_object_option = std::make_unique(text, value_ptr, key, default_value, true, flags); + color_object_option->set_description("Color option (should be described in more detail here)"); + add_option(std::move(color_object_option)); } void @@ -83,7 +89,9 @@ ObjectSettings::add_rgb(const std::string& text, Color* value_ptr, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, false, flags)); + auto rgb_option = std::make_unique(text, value_ptr, key, default_value, false, flags); + rgb_option->set_description("Color option (should be described in more detail here)"); + add_option(std::move(rgb_option)); } void @@ -92,7 +100,9 @@ ObjectSettings::add_bool(const std::string& text, bool* value_ptr, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, flags)); + auto bool_option = std::make_unique(text, value_ptr, key, default_value, flags); + bool_option->set_description("Bool option (should be described in more detail here)"); + add_option(std::move(bool_option)); } void @@ -101,7 +111,9 @@ ObjectSettings::add_float(const std::string& text, float* value_ptr, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, flags)); + auto float_option = std::make_unique(text, value_ptr, key, default_value, flags); + float_option->set_description("Float option (should be described in more detail here)"); + add_option(std::move(float_option)); } void @@ -110,7 +122,9 @@ ObjectSettings::add_int(const std::string& text, int* value_ptr, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, flags)); + auto int_option = std::make_unique(text, value_ptr, key, default_value, flags); + int_option->set_description("Int option (should be described in more detail here)"); + add_option(std::move(int_option)); } void diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 6a99e2c5eb..7c881f2cb8 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -896,30 +896,32 @@ EditorOverlayWidget::process_left_click() ObjectSettings os = m_hovered_object.get()->get_settings(); for(const auto& option : os.get_options()) { + auto text = option.get()->get_text(); + auto description = option.get()->get_description(); if (dynamic_cast(option.get())) { - m_editor.addControl(option.get()->get_text(), nullptr); + m_editor.addControl(text, nullptr, description); } else if (auto int_option = dynamic_cast(option.get())) { auto textbox = std::make_unique(); textbox.get()->set_rect(Rectf(0, 32, 200, 32)); textbox.get()->bind_value(int_option->get_value()); - m_editor.addControl(option.get()->get_text(), std::move(textbox)); + m_editor.addControl(text, std::move(textbox), description); } else if (auto float_option = dynamic_cast(option.get())) { auto textbox = std::make_unique(); textbox.get()->set_rect(Rectf(0, 32, 200, 32)); textbox.get()->bind_value(float_option->get_value()); - m_editor.addControl(option.get()->get_text(), std::move(textbox)); + m_editor.addControl(text, std::move(textbox), description); } else if (auto bool_option = dynamic_cast(option.get())) { auto checkbox = std::make_unique(); - checkbox.get()->set_rect(Rectf(0, 32, 20, 32)); + checkbox.get()->set_rect(Rectf(0, 32, 32, 32)); checkbox.get()->bind_value(bool_option->get_value()); - m_editor.addControl(option.get()->get_text(), std::move(checkbox)); + m_editor.addControl(text, std::move(checkbox), description); } else if (auto script_option = dynamic_cast(option.get())) { @@ -929,7 +931,7 @@ EditorOverlayWidget::process_left_click() MenuManager::instance().push_menu(std::make_unique(value_ptr)); }); button.get()->set_rect(Rectf(0, 32, 20, 32)); - m_editor.addControl(option.get()->get_text(), std::move(button)); + m_editor.addControl(text, std::move(button), description); } // else if (auto enum_option = dynamic_cast(option.get())) // { @@ -940,7 +942,7 @@ EditorOverlayWidget::process_left_click() auto textbox = std::make_unique(); textbox.get()->set_rect(Rectf(0, 32, 200, 32)); textbox.get()->put_text(option.get()->to_string()); - m_editor.addControl(option.get()->get_text(), std::move(textbox)); + m_editor.addControl(text, std::move(textbox), description); } } } From 9d1a362996d2774fad9491754588c80efd81c49a Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Thu, 3 Jul 2025 22:44:51 +0200 Subject: [PATCH 024/141] Add WIP code for viewing the text of a level --- src/editor/editor.cpp | 12 ++++++++++++ src/editor/overlay_widget.cpp | 6 +++--- src/supertux/level.hpp | 3 +-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 52ab0be74e..09239ab076 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -42,6 +42,7 @@ #include "editor/tool_icon.hpp" #include "gui/dialog.hpp" #include "gui/menu_manager.hpp" +#include "gui/menu_script.hpp" #include "gui/mousecursor.hpp" #include "math/util.hpp" #include "object/camera.hpp" @@ -224,6 +225,17 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_area_button)); m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_fill_button)); m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_same_button)); + + // auto code_widget = std::make_unique( + // "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { + // std::ostringstream level_ostream; + // Writer output_writer(level_ostream); + // m_level->save(output_writer); + // auto level_content = level_ostream.str(); + // MenuManager::instance().push_menu(std::make_unique(&level_content)); + // log_warning << level_content << std::endl; + // }); + // m_widgets.insert(m_widgets.begin() + 10, std::move(code_widget)); } Editor::~Editor() diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 7c881f2cb8..22f7779927 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -784,7 +784,7 @@ EditorOverlayWidget::add_path_node() m_edited_path->save_state(); Path::Node new_node(&m_edited_path->get_path()); - new_node.position = m_sector_pos; + new_node.position = m_sector_pos - Vector(200.f, 32.f); new_node.bezier_before = new_node.position; new_node.bezier_after = new_node.position; new_node.time = 1; @@ -821,11 +821,11 @@ EditorOverlayWidget::put_object() } else { - auto target_pos = m_sector_pos; + auto target_pos = m_sector_pos - Vector(200.f, 32.f); if (g_config->editor_snap_to_grid) { auto& snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; - target_pos = glm::floor(m_sector_pos / static_cast(snap_grid_size)) * static_cast(snap_grid_size); + target_pos = glm::floor(target_pos / static_cast(snap_grid_size)) * static_cast(snap_grid_size); } auto object = GameObjectFactory::instance().create(object_class, target_pos); diff --git a/src/supertux/level.hpp b/src/supertux/level.hpp index d5bb64ac3a..9caf51ce73 100644 --- a/src/supertux/level.hpp +++ b/src/supertux/level.hpp @@ -44,6 +44,7 @@ class Level final // saves to a levelfile void save(const std::string& filename, bool retry = false); void save(std::ostream& stream); + void save(Writer& writer); void add_sector(std::unique_ptr sector); inline const std::string& get_name() const { return m_name; } @@ -73,8 +74,6 @@ class Level final private: void initialize(); - - void save(Writer& writer); void load_old_format(const ReaderMapping& reader); public: From a06c500894b20c81bbd89cdf1166d1ce38c3b358 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Fri, 4 Jul 2025 11:50:25 +0200 Subject: [PATCH 025/141] Fix bugs in ControlTextbox when binding a value --- src/interface/control_textbox.cpp | 5 +++-- src/interface/control_textbox.hpp | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/interface/control_textbox.cpp b/src/interface/control_textbox.cpp index 89c94116f5..a195469ae3 100644 --- a/src/interface/control_textbox.cpp +++ b/src/interface/control_textbox.cpp @@ -315,16 +315,17 @@ ControlTextbox::event(const SDL_Event& ev) { bool ControlTextbox::parse_value(bool call_on_change /* = true (see header)*/) { + std::string new_str = get_contents(); + // Abort if we have a validation function for the string, and the function // says the string is invalid. if (m_validate_string) { - if (!m_validate_string(this, get_contents())) { + if (!m_validate_string(this, new_str)) { revert_value(); return false; } } - std::string new_str = get_string(); if (m_internal_string_backup != new_str) { m_internal_string_backup = new_str; diff --git a/src/interface/control_textbox.hpp b/src/interface/control_textbox.hpp index 7cccb722a0..2edf3d61f6 100644 --- a/src/interface/control_textbox.hpp +++ b/src/interface/control_textbox.hpp @@ -38,7 +38,16 @@ class ControlTextbox : public InterfaceControl virtual void update(float dt_sec) override; /** Binds a string to the textbox */ - inline void bind_string(std::string* value) { m_string = value; } + inline void bind_string(std::string* value) + { + m_string = value; + + if(value != nullptr) + { + m_internal_string_backup = *value; + revert_value(); + } + } /** Returns the full string held in m_charlist */ const std::string& get_string() const; From e926d9355d93b03511a70105b2b4758fd53be9c1 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Fri, 4 Jul 2025 11:50:51 +0200 Subject: [PATCH 026/141] Add textbox elements for string properties --- src/editor/overlay_widget.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 22f7779927..88ee400ae8 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -933,6 +933,13 @@ EditorOverlayWidget::process_left_click() button.get()->set_rect(Rectf(0, 32, 20, 32)); m_editor.addControl(text, std::move(button), description); } + else if (auto string_option = dynamic_cast(option.get())) + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->bind_string(string_option->get_value()); + m_editor.addControl(text, std::move(textbox), description); + } // else if (auto enum_option = dynamic_cast(option.get())) // { // auto dropdown = std::make_unique(); From 838cef7eb0289184c5adbb5ae350fa74eaf3bf95 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Fri, 4 Jul 2025 18:44:02 +0200 Subject: [PATCH 027/141] Rudimentary support for making editor buttons invisible on mode change --- src/editor/button_widget.hpp | 78 ++++++++++++++++++++++++++++++++++++ src/editor/editor.cpp | 49 ++++++++++++++++------ src/editor/editor.hpp | 12 +++--- 3 files changed, 121 insertions(+), 18 deletions(-) diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index 972b33bada..acd96b1aba 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -62,3 +62,81 @@ class ButtonWidget : public Widget ButtonWidget(const ButtonWidget&) = delete; ButtonWidget& operator=(const ButtonWidget&) = delete; }; + +class EditorToolbarButtonWidget : public ButtonWidget +{ +public: + EditorToolbarButtonWidget(SpritePtr sprite, const Vector& pos, std::function m_sig_click = {}) : + ButtonWidget(std::move(sprite), pos, m_sig_click), + m_tile_mode_visible(true), + m_object_mode_visible(true), + m_visible(true) + { + } + + EditorToolbarButtonWidget(const std::string& path, const Vector& pos, std::function callback = {}) : + ButtonWidget(SpriteManager::current()->create(path), pos, std::move(callback)), + m_tile_mode_visible(true), + m_object_mode_visible(true), + m_visible(true) + { + } + + virtual void draw(DrawingContext& context) override + { + if (!get_visible()) + return; + + ButtonWidget::draw(context); + } + + virtual void update(float dt_sec) override + { + if (!get_visible()) + return; + + ButtonWidget::update(dt_sec); + } + + virtual bool on_mouse_button_up(const SDL_MouseButtonEvent& button) override + { + if (!get_visible()) + return false; + + return ButtonWidget::on_mouse_button_up(button); + } + + virtual bool on_mouse_button_down(const SDL_MouseButtonEvent& button) override + { + if (!get_visible()) + return false; + + return ButtonWidget::on_mouse_button_down(button); + } + + virtual bool on_mouse_motion(const SDL_MouseMotionEvent& motion) override + { + if (!get_visible()) + return false; + + return ButtonWidget::on_mouse_motion(motion); + } + + void set_visible_in_tile_mode(bool visible) { m_tile_mode_visible = visible; } + bool get_visible_in_tile_mode() const { return m_tile_mode_visible; } + + void set_visible_in_object_mode(bool visible) { m_object_mode_visible = visible; } + bool get_visible_in_object_mode() const { return m_object_mode_visible; } + + void set_visible(bool visible) { m_visible = visible; } + bool get_visible() const { return m_visible; } + +private: + bool m_tile_mode_visible; + bool m_object_mode_visible; + bool m_visible; + +private: + EditorToolbarButtonWidget(const EditorToolbarButtonWidget&) = delete; + EditorToolbarButtonWidget& operator=(const EditorToolbarButtonWidget&) = delete; +}; diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 09239ab076..6919948c85 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -150,7 +150,7 @@ Editor::Editor() : m_widgets.push_back(std::move(layers_widget)); m_widgets.push_back(std::move(overlay_widget)); - auto grid_size_widget = std::make_unique("images/engine/editor/grid_button.png", + auto grid_size_widget = std::make_unique("images/engine/editor/grid_button.png", Vector(64, 0), [this] { auto& snap_grid_size = g_config->editor_selected_snap_grid_size; @@ -171,7 +171,7 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 2, std::move(grid_size_widget)); - auto play_button = std::make_unique("images/engine/editor/play_button.png", + auto play_button = std::make_unique("images/engine/editor/play_button.png", Vector(96, 0), [this] { m_test_request = true; }); play_button->set_help_text(_("Test level")); @@ -179,7 +179,7 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 3, std::move(play_button)); - auto save_button = std::make_unique("images/engine/editor/save.png", + auto save_button = std::make_unique("images/engine/editor/save.png", Vector(128, 0), [this] { save_level(); }); save_button->set_help_text(_("Save level")); @@ -187,46 +187,71 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 4, std::move(save_button)); - auto mode_button = std::make_unique("images/engine/editor/toggle_tile_object_mode.png", + auto mode_button = std::make_unique("images/engine/editor/toggle_tile_object_mode.png", Vector(160, 0), [this] { auto& tilebox = m_toolbox_widget->get_tilebox(); const auto& input_type = tilebox.get_input_type(); if (input_type == InputType::OBJECT) + { select_tilegroup(0); + for(const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + toolbar_button->set_visible(toolbar_button->get_visible_in_tile_mode()); + } + } + } else + { select_objectgroup(0); + for(const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + toolbar_button->set_visible(toolbar_button->get_visible_in_object_mode()); + } + } + } }); mode_button->set_help_text(_("Toggle between object and tile mode")); m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); - auto select_mode_mouse_button = std::make_unique( + auto select_mode_mouse_button = std::make_unique( "images/engine/editor/select-mode0.png", Vector(192, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(0); }); select_mode_mouse_button->set_help_text(_("Draw mode (The current tool applies to the tile under the mouse)")); - auto select_mode_area_button = std::make_unique( + select_mode_mouse_button->set_visible_in_object_mode(false); + + auto select_mode_area_button = std::make_unique( "images/engine/editor/select-mode1.png", Vector(224, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(1); }); select_mode_area_button->set_help_text(_("Box draw mode (The current tool applies to an area / box drawn with the mouse)")); - auto select_mode_fill_button = std::make_unique( + select_mode_area_button->set_visible_in_object_mode(false); + + auto select_mode_fill_button = std::make_unique( "images/engine/editor/select-mode2.png", Vector(256, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(2); }); select_mode_fill_button->set_help_text(_("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")); - auto select_mode_same_button = std::make_unique( + select_mode_fill_button->set_visible_in_object_mode(false); + + auto select_mode_same_button = std::make_unique( "images/engine/editor/select-mode3.png", Vector(288, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(3); }); select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); - + select_mode_same_button->set_visible_in_object_mode(false); + m_widgets.insert(m_widgets.begin() + 6, std::move(select_mode_mouse_button)); m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_area_button)); m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_fill_button)); m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_same_button)); - // auto code_widget = std::make_unique( + // auto code_widget = std::make_unique( // "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { // std::ostringstream level_ostream; // Writer output_writer(level_ostream); @@ -1175,10 +1200,10 @@ Editor::retoggle_undo_tracking() if (g_config->editor_undo_tracking && !m_undo_widget) { // Add undo/redo button widgets. - auto undo_button_widget = std::make_unique("images/engine/editor/undo.png", + auto undo_button_widget = std::make_unique("images/engine/editor/undo.png", Vector(0, 0), [this]{ undo(); }); undo_button_widget->set_help_text(_("Undo")); - auto redo_button_widget = std::make_unique("images/engine/editor/redo.png", + auto redo_button_widget = std::make_unique("images/engine/editor/redo.png", Vector(32, 0), [this]{ redo(); }); redo_button_widget->set_help_text(_("Redo")); diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 9c976d5947..fc43637a48 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -36,7 +36,7 @@ #include "util/string_util.hpp" #include "video/surface_ptr.hpp" -class ButtonWidget; +class EditorToolbarButtonWidget; class GameObject; class Level; class ObjectGroup; @@ -231,11 +231,11 @@ class Editor final : public Screen, std::vector > m_widgets; std::vector> m_controls; - ButtonWidget* m_undo_widget; - ButtonWidget* m_redo_widget; - ButtonWidget* m_grid_size_widget; - ButtonWidget* m_play_widget; - ButtonWidget* m_save_widget; + EditorToolbarButtonWidget* m_undo_widget; + EditorToolbarButtonWidget* m_redo_widget; + EditorToolbarButtonWidget* m_grid_size_widget; + EditorToolbarButtonWidget* m_play_widget; + EditorToolbarButtonWidget* m_save_widget; EditorOverlayWidget* m_overlay_widget; EditorToolboxWidget* m_toolbox_widget; From 553363090f51d8679bd299269edc0b3aab00f3bc Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 01:30:58 +0200 Subject: [PATCH 028/141] Add test from here button to sidebar --- src/editor/overlay_widget.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 88ee400ae8..3bc55877bf 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -37,6 +37,7 @@ #include "math/bezier.hpp" #include "object/camera.hpp" #include "object/path_gameobject.hpp" +#include "object/spawnpoint.hpp" #include "object/tilemap.hpp" #include "supertux/gameconfig.hpp" #include "supertux/autotile.hpp" @@ -893,7 +894,10 @@ EditorOverlayWidget::process_left_click() // WIP: auto& controls = m_editor.get_controls(); controls.clear(); - ObjectSettings os = m_hovered_object.get()->get_settings(); + + auto hovered_object = m_hovered_object.get(); + ObjectSettings os = hovered_object->get_settings(); + for(const auto& option : os.get_options()) { auto text = option.get()->get_text(); @@ -940,6 +944,24 @@ EditorOverlayWidget::process_left_click() textbox.get()->bind_string(string_option->get_value()); m_editor.addControl(text, std::move(textbox), description); } + else if (auto test_from_here_option = dynamic_cast(option.get())) + { + auto button = std::make_unique(_("Test")); + button->set_rect(Rectf(0, 32, 200, 32)); + button.get()->m_on_change = std::function([hovered_object, this]() { + auto spawnpoint = dynamic_cast(hovered_object); + if (spawnpoint == nullptr) + return; + + // TODO: Pressing the return key from within a game session automatically + // triggers this button again if it's previously been pushed. This needs + // to get fixed. + auto sector_name = Editor::current()->get_sector()->get_name(); + m_editor.m_test_pos = std::make_pair(sector_name, spawnpoint->get_pos()); + m_editor.m_test_request = true; + }); + m_editor.addControl(text, std::move(button), description); + } // else if (auto enum_option = dynamic_cast(option.get())) // { // auto dropdown = std::make_unique(); From c180d53d3d852a397f09585fe896d49ee6bea2ea Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 02:38:46 +0200 Subject: [PATCH 029/141] First support for enum options --- src/editor/object_option.hpp | 4 ++++ src/editor/overlay_widget.cpp | 17 +++++++++++++---- src/interface/control_enum.hpp | 2 ++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index 56ab236dd5..9e0f5b662a 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -259,6 +259,10 @@ class EnumObjectOption final : public ObjectOption virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + const std::vector& get_labels() const { return m_labels; } + const std::vector& get_symbols() const { return m_symbols; } + const std::optional& get_default_value() const { return m_default_value; } + private: const std::vector m_labels; const std::vector m_symbols; diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 3bc55877bf..8bf3e32b8e 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -962,10 +962,19 @@ EditorOverlayWidget::process_left_click() }); m_editor.addControl(text, std::move(button), description); } - // else if (auto enum_option = dynamic_cast(option.get())) - // { - // auto dropdown = std::make_unique(); - // } + else if (auto enum_option = dynamic_cast(option.get())) + { + auto labels = enum_option->get_labels(); + auto dropdown = std::make_unique>(); + + for(int i = 0; i < labels.size(); i++) + { + dropdown->add_option(i, labels[i]); + } + + dropdown.get()->bind_value(enum_option->get_value()); + m_editor.addControl(text, std::move(dropdown), description); + } else { auto textbox = std::make_unique(); diff --git a/src/interface/control_enum.hpp b/src/interface/control_enum.hpp index 97fca3a223..62c9ebe0c7 100644 --- a/src/interface/control_enum.hpp +++ b/src/interface/control_enum.hpp @@ -189,6 +189,8 @@ ControlEnum::on_mouse_button_down(const SDL_MouseButtonEvent& button) break; } + m_has_focus = false; + m_open_list = false; } else { log_warning << "Clicked on control enum inside dropdown but at invalid position (" << pos << " for a size of " << m_options.size() << ")" << std::endl; From 3f0fed5fea0a0669c583b55013dfe5275afac566 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 03:37:55 +0200 Subject: [PATCH 030/141] Try to update object after changing e.g. crusher size --- src/editor/overlay_widget.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 8bf3e32b8e..e37c2d926b 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -973,6 +973,13 @@ EditorOverlayWidget::process_left_click() } dropdown.get()->bind_value(enum_option->get_value()); + dropdown.get()->m_on_change = std::function([hovered_object, this]() { + // TODO: Updating the object doesn't work every time. + // Investigate why this is the case! + hovered_object->after_editor_set(); + hovered_object->check_state(); + m_editor.m_reactivate_request = true; + }); m_editor.addControl(text, std::move(dropdown), description); } else From 644c6cebace5380a71a173c2daf3d7d7b8247619 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 04:26:27 +0200 Subject: [PATCH 031/141] Move `update_properties_panel` method out into own function --- src/editor/overlay_widget.cpp | 214 ++++++++++++++++++---------------- src/editor/overlay_widget.hpp | 1 + 2 files changed, 113 insertions(+), 102 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index e37c2d926b..e068a1fe9e 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -697,6 +697,114 @@ EditorOverlayWidget::show_object_menu(GameObject& object) MenuManager::instance().push_menu(std::move(menu)); } +void +EditorOverlayWidget::update_properties_panel(GameObject* object) +{ + auto& controls = m_editor.get_controls(); + controls.clear(); + + if (object == nullptr) + return; + + auto hovered_object = object; + ObjectSettings os = hovered_object->get_settings(); + + for(const auto& option : os.get_options()) + { + auto text = option.get()->get_text(); + auto description = option.get()->get_description(); + if (dynamic_cast(option.get())) + { + m_editor.addControl(text, nullptr, description); + } + else if (auto int_option = dynamic_cast(option.get())) + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->bind_value(int_option->get_value()); + m_editor.addControl(text, std::move(textbox), description); + } + else if (auto float_option = dynamic_cast(option.get())) + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->bind_value(float_option->get_value()); + m_editor.addControl(text, std::move(textbox), description); + } + else if (auto bool_option = dynamic_cast(option.get())) + { + auto checkbox = std::make_unique(); + checkbox.get()->set_rect(Rectf(0, 32, 32, 32)); + checkbox.get()->bind_value(bool_option->get_value()); + m_editor.addControl(text, std::move(checkbox), description); + } + else if (auto script_option = dynamic_cast(option.get())) + { + auto button = std::make_unique(_("Edit...")); + const auto value_ptr = script_option->get_value(); + button.get()->m_on_change = std::function([value_ptr]() { + MenuManager::instance().push_menu(std::make_unique(value_ptr)); + }); + button.get()->set_rect(Rectf(0, 32, 20, 32)); + m_editor.addControl(text, std::move(button), description); + } + else if (auto string_option = dynamic_cast(option.get())) + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->bind_string(string_option->get_value()); + m_editor.addControl(text, std::move(textbox), description); + } + else if (auto test_from_here_option = dynamic_cast(option.get())) + { + auto button = std::make_unique(_("Test")); + button->set_rect(Rectf(0, 32, 200, 32)); + button.get()->m_on_change = std::function([hovered_object, this]() { + auto spawnpoint = dynamic_cast(hovered_object); + if (spawnpoint == nullptr) + return; + + // TODO: Pressing the return key from within a game session automatically + // triggers this button again if it's previously been pushed. This needs + // to get fixed. + auto sector_name = Editor::current()->get_sector()->get_name(); + m_editor.m_test_pos = std::make_pair(sector_name, spawnpoint->get_pos()); + m_editor.m_test_request = true; + }); + m_editor.addControl(text, std::move(button), description); + } + else if (auto enum_option = dynamic_cast(option.get())) + { + auto labels = enum_option->get_labels(); + auto dropdown = std::make_unique>(); + + for(int i = 0; i < labels.size(); i++) + { + dropdown->add_option(i, labels[i]); + } + + dropdown.get()->bind_value(enum_option->get_value()); + dropdown.get()->m_on_change = std::function([hovered_object, this]() { + if (hovered_object == nullptr) + return; + // TODO: Updating the object doesn't work every time. + // Investigate why this is the case! + hovered_object->after_editor_set(); + hovered_object->check_state(); + update_properties_panel(hovered_object); + }); + m_editor.addControl(text, std::move(dropdown), description); + } + else + { + auto textbox = std::make_unique(); + textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + textbox.get()->put_text(option.get()->to_string()); + m_editor.addControl(text, std::move(textbox), description); + } + } +} + void EditorOverlayWidget::move_object() { @@ -887,110 +995,12 @@ EditorOverlayWidget::process_left_click() break; case EditorTilebox::InputType::NONE: - case EditorTilebox::InputType::OBJECT: - - if (m_hovered_object.get() != nullptr) - { - // WIP: - auto& controls = m_editor.get_controls(); - controls.clear(); - - auto hovered_object = m_hovered_object.get(); - ObjectSettings os = hovered_object->get_settings(); - - for(const auto& option : os.get_options()) + case EditorTilebox::InputType::OBJECT: + if (m_hovered_object.get() != nullptr) { - auto text = option.get()->get_text(); - auto description = option.get()->get_description(); - if (dynamic_cast(option.get())) - { - m_editor.addControl(text, nullptr, description); - } - else if (auto int_option = dynamic_cast(option.get())) - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->bind_value(int_option->get_value()); - m_editor.addControl(text, std::move(textbox), description); - } - else if (auto float_option = dynamic_cast(option.get())) - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->bind_value(float_option->get_value()); - m_editor.addControl(text, std::move(textbox), description); - } - else if (auto bool_option = dynamic_cast(option.get())) - { - auto checkbox = std::make_unique(); - checkbox.get()->set_rect(Rectf(0, 32, 32, 32)); - checkbox.get()->bind_value(bool_option->get_value()); - m_editor.addControl(text, std::move(checkbox), description); - } - else if (auto script_option = dynamic_cast(option.get())) - { - auto button = std::make_unique(_("Edit...")); - const auto value_ptr = script_option->get_value(); - button.get()->m_on_change = std::function([value_ptr]() { - MenuManager::instance().push_menu(std::make_unique(value_ptr)); - }); - button.get()->set_rect(Rectf(0, 32, 20, 32)); - m_editor.addControl(text, std::move(button), description); - } - else if (auto string_option = dynamic_cast(option.get())) - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->bind_string(string_option->get_value()); - m_editor.addControl(text, std::move(textbox), description); - } - else if (auto test_from_here_option = dynamic_cast(option.get())) - { - auto button = std::make_unique(_("Test")); - button->set_rect(Rectf(0, 32, 200, 32)); - button.get()->m_on_change = std::function([hovered_object, this]() { - auto spawnpoint = dynamic_cast(hovered_object); - if (spawnpoint == nullptr) - return; - - // TODO: Pressing the return key from within a game session automatically - // triggers this button again if it's previously been pushed. This needs - // to get fixed. - auto sector_name = Editor::current()->get_sector()->get_name(); - m_editor.m_test_pos = std::make_pair(sector_name, spawnpoint->get_pos()); - m_editor.m_test_request = true; - }); - m_editor.addControl(text, std::move(button), description); - } - else if (auto enum_option = dynamic_cast(option.get())) - { - auto labels = enum_option->get_labels(); - auto dropdown = std::make_unique>(); - - for(int i = 0; i < labels.size(); i++) - { - dropdown->add_option(i, labels[i]); - } - - dropdown.get()->bind_value(enum_option->get_value()); - dropdown.get()->m_on_change = std::function([hovered_object, this]() { - // TODO: Updating the object doesn't work every time. - // Investigate why this is the case! - hovered_object->after_editor_set(); - hovered_object->check_state(); - m_editor.m_reactivate_request = true; - }); - m_editor.addControl(text, std::move(dropdown), description); - } - else - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->put_text(option.get()->to_string()); - m_editor.addControl(text, std::move(textbox), description); - } + update_properties_panel(m_hovered_object.get()); } - } + switch (m_editor.get_tileselect_move_mode()) { case 0: diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index 635565d262..f8114434b7 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -97,6 +97,7 @@ class EditorOverlayWidget final : public Widget void clone_object(); void hover_object(); void show_object_menu(GameObject& object); + void update_properties_panel(GameObject* object); void select_object(); void add_path_node(); From ff334edde12c910714263a31e84ed9aa9a5a8f34 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 04:47:33 +0200 Subject: [PATCH 032/141] WIP: Properties for clicked layer items --- src/editor/editor.hpp | 4 ++++ src/editor/layers_widget.cpp | 3 +++ src/editor/overlay_widget.hpp | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index fc43637a48..ae88525f20 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -157,6 +157,10 @@ class Editor final : public Screen, m_overlay_widget->edit_path(path, new_marked_object); } + void update_properties_panel(GameObject* object) { + m_overlay_widget->update_properties_panel(object); + } + void add_layer(GameObject* layer) { m_layers_widget->add_layer(layer); } inline TileMap* get_selected_tilemap() const { return m_layers_widget->get_selected_tilemap(); } diff --git a/src/editor/layers_widget.cpp b/src/editor/layers_widget.cpp index 600ade84a2..acc6de471d 100644 --- a/src/editor/layers_widget.cpp +++ b/src/editor/layers_widget.cpp @@ -218,10 +218,12 @@ EditorLayersWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) if (tilemap) { set_selected_tilemap(tilemap); m_editor.edit_path(tilemap->get_path_gameobject(), tilemap); + m_editor.update_properties_panel(tilemap); } else { auto cam = dynamic_cast(m_layer_icons[m_hovered_layer]->get_layer()); if (cam) { m_editor.edit_path(cam->get_path_gameobject(), cam); + m_editor.update_properties_panel(cam); } } } @@ -237,6 +239,7 @@ EditorLayersWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) auto om = std::make_unique(m_layer_icons[m_hovered_layer]->get_layer()); m_editor.m_deactivate_request = true; MenuManager::instance().push_menu(std::move(om)); + m_editor.update_properties_panel(m_layer_icons[m_hovered_layer]->get_layer()); return true; } else { return false; diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index f8114434b7..feb117b619 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -65,6 +65,7 @@ class EditorOverlayWidget final : public Widget void update_pos(); void update_autotileset(); + void update_properties_panel(GameObject* object); void delete_markers(); void update_node_iterators(); void on_level_change(); @@ -97,7 +98,6 @@ class EditorOverlayWidget final : public Widget void clone_object(); void hover_object(); void show_object_menu(GameObject& object); - void update_properties_panel(GameObject* object); void select_object(); void add_path_node(); From 039d9728e3907511308ab189b9062e320771e546 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 11:12:11 +0200 Subject: [PATCH 033/141] Fix Checkbox position and dimensions --- src/editor/overlay_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index e068a1fe9e..8e458a1c4f 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -734,7 +734,7 @@ EditorOverlayWidget::update_properties_panel(GameObject* object) else if (auto bool_option = dynamic_cast(option.get())) { auto checkbox = std::make_unique(); - checkbox.get()->set_rect(Rectf(0, 32, 32, 32)); + checkbox.get()->set_rect(Rectf(140.f, 0.f, 160.f, 20.f)); checkbox.get()->bind_value(bool_option->get_value()); m_editor.addControl(text, std::move(checkbox), description); } From 745b923581936b8a62ed54b9a9a6bee9cf06dd05 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 11:30:35 +0200 Subject: [PATCH 034/141] Fix tooltip z position and use normal font for better readability --- src/editor/button_widget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index b82aa7ff70..fc5384fc46 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -56,12 +56,12 @@ ButtonWidget::draw(DrawingContext& context) if (m_hover && !m_help_text.empty()) { - const auto& font = Resources::small_font; + const auto& font = Resources::control_font; const auto text_height = font->get_text_height(m_help_text); const auto text_width = font->get_text_width(m_help_text); const auto text_rect = Rectf(m_mouse_pos + Vector(32, 32), m_mouse_pos + Vector(32, 32) + Vector(text_width, text_height)); - context.color().draw_filled_rect(text_rect, Color::BLACK, LAYER_GUI - 5); - context.color().draw_text(font, m_help_text, m_mouse_pos + Vector(32, 32), FontAlignment::ALIGN_LEFT, LAYER_GUI - 5); + context.color().draw_filled_rect(text_rect, Color::BLACK, INT_MAX); + context.color().draw_text(font, m_help_text, m_mouse_pos + Vector(32, 32), FontAlignment::ALIGN_LEFT, INT_MAX); } } From 02087734f21c99e2014571856f2725f9a8872927 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 14:13:10 +0200 Subject: [PATCH 035/141] WIP: Direction property left sidebar control --- src/editor/object_option.hpp | 5 +++++ src/editor/overlay_widget.cpp | 21 +++++++++++++++++++++ src/interface/control_textbox.cpp | 7 +++++-- src/interface/control_textbox_float.cpp | 1 + src/interface/control_textbox_int.cpp | 1 + 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index 9e0f5b662a..1b1698c65d 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -572,6 +572,11 @@ class DirectionOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + + const std::vector& get_possible_directions() const + { + return m_possible_directions; + } private: std::vector m_possible_directions; diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 8e458a1c4f..cb7972294d 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -795,6 +795,27 @@ EditorOverlayWidget::update_properties_panel(GameObject* object) }); m_editor.addControl(text, std::move(dropdown), description); } + else if(auto direction_option = dynamic_cast(option.get())) + { + auto directions = direction_option->get_possible_directions(); + auto dropdown = std::make_unique>(); + for (const auto& direction : directions) + { + dropdown->add_option(direction, dir_to_translated_string(direction)); + } + dropdown->bind_value(direction_option->get_value()); + dropdown.get()->m_on_change = std::function([hovered_object, this]() { + if (hovered_object == nullptr) + return; + // TODO: Updating the object doesn't work every time. + // Investigate why this is the case! + hovered_object->after_editor_set(); + hovered_object->check_state(); + update_properties_panel(hovered_object); + }); + + m_editor.addControl(text, std::move(dropdown), description); + } else { auto textbox = std::make_unique(); diff --git a/src/interface/control_textbox.cpp b/src/interface/control_textbox.cpp index a195469ae3..93adebb95b 100644 --- a/src/interface/control_textbox.cpp +++ b/src/interface/control_textbox.cpp @@ -441,15 +441,18 @@ ControlTextbox::fits(const std::string& text) const void ControlTextbox::recenter_offset() { + auto contents = get_contents(); + auto visible_contents = get_contents_visible(); + while (m_caret_pos < m_current_offset && m_current_offset > 0) { m_current_offset--; } - while (m_caret_pos > m_current_offset + int(get_contents_visible().size()) && m_current_offset < int(get_contents().size())) { + while (m_caret_pos > m_current_offset + int(visible_contents.size()) && m_current_offset < int(contents.size())) { m_current_offset++; } - while (m_current_offset > 0 && fits(get_contents().substr(m_current_offset - 1))) { + while (m_current_offset > 0 && fits(contents.substr(m_current_offset - 1))) { m_current_offset--; } } diff --git a/src/interface/control_textbox_float.cpp b/src/interface/control_textbox_float.cpp index 4557461cba..93f75630ee 100644 --- a/src/interface/control_textbox_float.cpp +++ b/src/interface/control_textbox_float.cpp @@ -20,6 +20,7 @@ #include ControlTextboxFloat::ControlTextboxFloat() : + ControlTextbox(), m_validate_float(), m_value(nullptr) { diff --git a/src/interface/control_textbox_int.cpp b/src/interface/control_textbox_int.cpp index b79dd27cde..721196cc8b 100644 --- a/src/interface/control_textbox_int.cpp +++ b/src/interface/control_textbox_int.cpp @@ -20,6 +20,7 @@ #include ControlTextboxInt::ControlTextboxInt() : + ControlTextbox(), m_validate_int(), m_value(nullptr) { From 483d7b5f322a14c0bcb769b7df4f5a51d1f2ab89 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 14:26:18 +0200 Subject: [PATCH 036/141] Delete properties when object is deleted --- src/editor/overlay_widget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index cb7972294d..8fb0558693 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -868,6 +868,7 @@ EditorOverlayWidget::move_object() void EditorOverlayWidget::rubber_object() { + update_properties_panel(nullptr); if (!m_edited_path) { delete_markers(); } From 7ed9eda1d954931bbcc3221e2d04732138940037 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 5 Jul 2025 22:19:40 +0200 Subject: [PATCH 037/141] Add mouse "select" button --- src/editor/editor.cpp | 24 ++++++++++++++++-------- src/editor/toolbox_widget.cpp | 24 ++++++++++++++++++------ src/editor/toolbox_widget.hpp | 3 +++ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 6919948c85..f385e0a6b2 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -217,39 +217,47 @@ Editor::Editor() : mode_button->set_help_text(_("Toggle between object and tile mode")); m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); + + auto mouse_select_button = std::make_unique( + "images/engine/editor/arrow.png", Vector(192, 0), [this]() { + m_toolbox_widget->set_mouse_tool(); + } + ); + mouse_select_button->set_help_text(_("Toggle between add and remove mode")); auto select_mode_mouse_button = std::make_unique( - "images/engine/editor/select-mode0.png", Vector(192, 0), [this] { + "images/engine/editor/select-mode0.png", Vector(224, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(0); }); select_mode_mouse_button->set_help_text(_("Draw mode (The current tool applies to the tile under the mouse)")); select_mode_mouse_button->set_visible_in_object_mode(false); auto select_mode_area_button = std::make_unique( - "images/engine/editor/select-mode1.png", Vector(224, 0), [this] { + "images/engine/editor/select-mode1.png", Vector(256, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(1); }); select_mode_area_button->set_help_text(_("Box draw mode (The current tool applies to an area / box drawn with the mouse)")); select_mode_area_button->set_visible_in_object_mode(false); auto select_mode_fill_button = std::make_unique( - "images/engine/editor/select-mode2.png", Vector(256, 0), [this] { + "images/engine/editor/select-mode2.png", Vector(288, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(2); }); select_mode_fill_button->set_help_text(_("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")); select_mode_fill_button->set_visible_in_object_mode(false); auto select_mode_same_button = std::make_unique( - "images/engine/editor/select-mode3.png", Vector(288, 0), [this] { + "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(3); }); select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); select_mode_same_button->set_visible_in_object_mode(false); - m_widgets.insert(m_widgets.begin() + 6, std::move(select_mode_mouse_button)); - m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_area_button)); - m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_fill_button)); - m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_same_button)); + m_widgets.insert(m_widgets.begin() + 6, std::move(mouse_select_button)); + m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_mouse_button)); + m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_area_button)); + m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_fill_button)); + m_widgets.insert(m_widgets.begin() + 10, std::move(select_mode_same_button)); // auto code_widget = std::make_unique( // "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index f0b58c7005..d9a70184e5 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -151,10 +151,7 @@ EditorToolboxWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) switch (m_hovered_tool) { case 0: - m_tilebox->get_tiles()->set_tile(0); - m_tilebox->set_object(""); - m_editor.update_autotileset(); - update_mouse_icon(); + set_rubber_tool(); break; case 1: @@ -181,8 +178,7 @@ EditorToolboxWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) break; case 3: - m_tilebox->set_object("#move"); - update_mouse_icon(); + set_mouse_tool(); break; default: @@ -200,6 +196,22 @@ EditorToolboxWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) } } +void +EditorToolboxWidget::set_rubber_tool() +{ + m_tilebox->set_object(""); + m_tilebox->get_tiles()->set_tile(0); + m_editor.update_autotileset(); + update_mouse_icon(); +} + +void +EditorToolboxWidget::set_mouse_tool() +{ + m_tilebox->set_object("#move"); + update_mouse_icon(); +} + bool EditorToolboxWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) { diff --git a/src/editor/toolbox_widget.hpp b/src/editor/toolbox_widget.hpp index f5bf17c5b3..a9021888ff 100644 --- a/src/editor/toolbox_widget.hpp +++ b/src/editor/toolbox_widget.hpp @@ -62,6 +62,9 @@ class EditorToolboxWidget final : public Widget void set_tileselect_select_mode(int mode); + void set_mouse_tool(); + void set_rubber_tool(); + inline EditorTilebox& get_tilebox() const { return *m_tilebox; } inline bool has_mouse_focus() const { return m_has_mouse_focus; } From 08627fa1b95798be21b4031471bd8d2571528087 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 6 Jul 2025 00:55:47 +0200 Subject: [PATCH 038/141] Add object selection modes to top menu bar --- src/editor/editor.cpp | 22 ++++++++++++++++++++++ src/editor/toolbox_widget.cpp | 7 +++++++ src/editor/toolbox_widget.hpp | 1 + 3 files changed, 30 insertions(+) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index f385e0a6b2..0c9c28b6d6 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -225,6 +225,9 @@ Editor::Editor() : ); mouse_select_button->set_help_text(_("Toggle between add and remove mode")); + /** + * ============= Tools only applicable for Tile mode ===================== + */ auto select_mode_mouse_button = std::make_unique( "images/engine/editor/select-mode0.png", Vector(224, 0), [this] { m_toolbox_widget->set_tileselect_select_mode(0); @@ -253,11 +256,30 @@ Editor::Editor() : select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); select_mode_same_button->set_visible_in_object_mode(false); + /** + * ============= Tile tools end / Object tools begin ===================== + */ + auto select_mode = std::make_unique( + "images/engine/editor/move-mode0.png", Vector(224, 0), [this] { + m_toolbox_widget->set_tileselect_move_mode(0); + }); + select_mode->set_help_text(_("Select mode (The object under the mouse gets selected)")); + select_mode->set_visible_in_tile_mode(false); + + auto duplicate_mode = std::make_unique( + "images/engine/editor/move-mode1.png", Vector(256, 0), [this] { + m_toolbox_widget->set_tileselect_move_mode(1); + }); + duplicate_mode->set_help_text(_("Duplicate mode (The object under the mouse gets duplicated)")); + duplicate_mode->set_visible_in_tile_mode(false); + m_widgets.insert(m_widgets.begin() + 6, std::move(mouse_select_button)); m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_mouse_button)); m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_area_button)); m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_fill_button)); m_widgets.insert(m_widgets.begin() + 10, std::move(select_mode_same_button)); + m_widgets.insert(m_widgets.begin() + 11, std::move(select_mode)); + m_widgets.insert(m_widgets.begin() + 12, std::move(duplicate_mode)); // auto code_widget = std::make_unique( // "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index d9a70184e5..d7d8d91781 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -325,6 +325,13 @@ EditorToolboxWidget::get_tileselect_move_mode() const return m_move_mode->get_mode(); } +void +EditorToolboxWidget::set_tileselect_move_mode(int mode) +{ + m_move_mode->set_mode(mode); + update_mouse_icon(); +} + Vector EditorToolboxWidget::get_tool_coords(int pos) const { diff --git a/src/editor/toolbox_widget.hpp b/src/editor/toolbox_widget.hpp index a9021888ff..f9192194f2 100644 --- a/src/editor/toolbox_widget.hpp +++ b/src/editor/toolbox_widget.hpp @@ -59,6 +59,7 @@ class EditorToolboxWidget final : public Widget int get_tileselect_move_mode() const; void update_mouse_icon(); + void set_tileselect_move_mode(int mode); void set_tileselect_select_mode(int mode); From 32d2f188b6bc0f2fa2bf6c80d4397c4f4bf313f4 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 6 Jul 2025 01:13:58 +0200 Subject: [PATCH 039/141] Move duplicated objects down by half a tile, change overlay texts --- src/editor/editor.cpp | 4 ++-- src/editor/overlay_widget.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 0c9c28b6d6..99af8631de 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -263,14 +263,14 @@ Editor::Editor() : "images/engine/editor/move-mode0.png", Vector(224, 0), [this] { m_toolbox_widget->set_tileselect_move_mode(0); }); - select_mode->set_help_text(_("Select mode (The object under the mouse gets selected)")); + select_mode->set_help_text(_("Select mode (Clicking selects the object under the mouse)")); select_mode->set_visible_in_tile_mode(false); auto duplicate_mode = std::make_unique( "images/engine/editor/move-mode1.png", Vector(256, 0), [this] { m_toolbox_widget->set_tileselect_move_mode(1); }); - duplicate_mode->set_help_text(_("Duplicate mode (The object under the mouse gets duplicated)")); + duplicate_mode->set_help_text(_("Duplicate mode (Clicking duplicates the object under the mouse)")); duplicate_mode->set_visible_in_tile_mode(false); m_widgets.insert(m_widgets.begin() + 6, std::move(mouse_select_button)); diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 8fb0558693..36df9801a0 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -679,6 +679,13 @@ EditorOverlayWidget::clone_object() if (path_object) path_object->editor_clone_path(dynamic_cast(m_hovered_object.get())->get_path_gameobject()); + if (auto moving_object = dynamic_cast(obj.get())) + { + // Move cloned objects half a tile down so the user gets + // a visual feedback that the object was cloned + moving_object->move(Vector(16, 16)); + } + m_dragged_object = static_cast(&m_editor.get_sector()->add_object(std::move(obj))); m_dragged_object->after_editor_set(); } From 466a466eb8b24fc7eb093871697656f2c256ec8c Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 6 Jul 2025 01:29:25 +0200 Subject: [PATCH 040/141] Small code style [ci skip] --- src/editor/overlay_widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 36df9801a0..1546e21e76 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -1789,8 +1789,8 @@ Vector EditorOverlayWidget::tile_screen_pos(const Vector& tp, int tile_size) const { Vector sp = tp_to_sp(tp, tile_size); - return (sp - m_editor.get_sector()->get_camera().get_translation()) * - m_editor.get_sector()->get_camera().get_current_scale(); + auto& camera = m_editor.get_sector()->get_camera(); + return (sp - camera.get_translation()) * camera.get_current_scale(); } Vector From 776185b00e0dfdea6ce423d795a4387cefd26692 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 6 Jul 2025 03:01:55 +0200 Subject: [PATCH 041/141] Set most buttons invisible, only show them on mode select --- src/editor/editor.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 99af8631de..1d65ef51c5 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -234,6 +234,7 @@ Editor::Editor() : }); select_mode_mouse_button->set_help_text(_("Draw mode (The current tool applies to the tile under the mouse)")); select_mode_mouse_button->set_visible_in_object_mode(false); + select_mode_mouse_button->set_visible(false); auto select_mode_area_button = std::make_unique( "images/engine/editor/select-mode1.png", Vector(256, 0), [this] { @@ -241,6 +242,7 @@ Editor::Editor() : }); select_mode_area_button->set_help_text(_("Box draw mode (The current tool applies to an area / box drawn with the mouse)")); select_mode_area_button->set_visible_in_object_mode(false); + select_mode_area_button->set_visible(false); auto select_mode_fill_button = std::make_unique( "images/engine/editor/select-mode2.png", Vector(288, 0), [this] { @@ -248,6 +250,7 @@ Editor::Editor() : }); select_mode_fill_button->set_help_text(_("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")); select_mode_fill_button->set_visible_in_object_mode(false); + select_mode_fill_button->set_visible(false); auto select_mode_same_button = std::make_unique( "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { @@ -255,6 +258,7 @@ Editor::Editor() : }); select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); select_mode_same_button->set_visible_in_object_mode(false); + select_mode_same_button->set_visible(false); /** * ============= Tile tools end / Object tools begin ===================== @@ -265,6 +269,7 @@ Editor::Editor() : }); select_mode->set_help_text(_("Select mode (Clicking selects the object under the mouse)")); select_mode->set_visible_in_tile_mode(false); + select_mode->set_visible(false); auto duplicate_mode = std::make_unique( "images/engine/editor/move-mode1.png", Vector(256, 0), [this] { @@ -272,6 +277,7 @@ Editor::Editor() : }); duplicate_mode->set_help_text(_("Duplicate mode (Clicking duplicates the object under the mouse)")); duplicate_mode->set_visible_in_tile_mode(false); + duplicate_mode->set_visible(false); m_widgets.insert(m_widgets.begin() + 6, std::move(mouse_select_button)); m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_mouse_button)); From 6926df0dfb4965fe2e4249fd1242fa000566ac5c Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 18:11:30 -0400 Subject: [PATCH 042/141] Merge some stuff from the editor2 branch Some things like level playing and loading will be temporarily broken. --- src/editor/editor.cpp | 57 +++++++-- src/editor/editor.hpp | 7 +- src/editor/object_option.cpp | 9 ++ src/editor/object_option.hpp | 1 + src/editor/object_settings.cpp | 11 ++ src/editor/object_settings.hpp | 1 + src/object/spawnpoint.cpp | 4 +- src/supertux/game_manager.cpp | 17 +++ src/supertux/game_manager.hpp | 3 + src/supertux/game_object.cpp | 25 ++++ src/supertux/game_object.hpp | 1 + src/supertux/game_object_manager.cpp | 27 +++++ src/supertux/game_object_manager.hpp | 1 + src/supertux/game_session.cpp | 112 ++++++++++++------ src/supertux/game_session.hpp | 13 +- src/supertux/level.cpp | 29 +++++ src/supertux/level.hpp | 4 +- .../menu/editor_levelset_select_menu.cpp | 3 + src/supertux/sector.cpp | 16 ++- src/supertux/sector.hpp | 1 + src/supertux/sector_base.cpp | 8 ++ src/supertux/sector_base.hpp | 3 +- src/util/fade_helper.cpp | 11 ++ src/util/fade_helper.hpp | 3 +- 24 files changed, 310 insertions(+), 57 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 8eb173f75c..cf116ff3f4 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -75,6 +75,8 @@ #include "video/surface.hpp" #include "video/video_system.hpp" #include "video/viewport.hpp" +#include "supertux/sector.hpp" +#include "supertux/sector_parser.hpp" static const float CAMERA_MIN_ZOOM = 0.5f; static const float CAMERA_MAX_ZOOM = 3.0f; @@ -112,6 +114,7 @@ Editor::Editor() : m_test_request(false), m_particle_editor_request(false), m_test_pos(), + m_temp_level(true), m_particle_editor_filename(), m_ctrl_pressed(false), m_sector(), @@ -303,6 +306,19 @@ Editor::~Editor() { } +void +Editor::level_from_nothing() +{ + m_level = std::make_unique(false); + m_level->m_name = "Supertux Level"; + m_level->m_tileset = "images/tiles.strf"; + auto sector = SectorParser::from_nothing(*m_level); + sector->set_name(DEFAULT_SECTOR_NAME); + m_level->add_sector(std::move(sector)); + m_level->initialize(); + //m_reload_request = true; +} + void Editor::queue_layers_refresh() { @@ -374,7 +390,7 @@ void Editor::update(float dt_sec, const Controller& controller) { // Auto-save (interval). - if (m_level) { + if (m_level && !m_temp_level) { m_time_since_last_save += dt_sec; if (m_time_since_last_save >= static_cast(std::max( g_config->editor_autosave_frequency, 1)) * 60.f) { @@ -485,6 +501,9 @@ Editor::update(float dt_sec, const Controller& controller) void Editor::remove_autosave_file() { + if (m_temp_level) + return; + // Clear the auto-save file. if (!m_autosave_levelfile.empty()) { @@ -502,6 +521,9 @@ Editor::remove_autosave_file() void Editor::save_level(const std::string& filename, bool switch_file) { + if (m_temp_level) + return; + auto file = !filename.empty() ? filename : m_levelfile; if (switch_file) @@ -540,6 +562,14 @@ Editor::test_level(const std::optional>& test_pos { Tile::draw_editor_images = false; Compositor::s_render_lighting = true; + + m_leveltested = true; + if ((m_level && m_levelfile.empty()) || m_levelfile == "") + { + GameManager::current()->start_level(m_level.get(), test_pos); + return; + } + std::string backup_filename = get_autosave_from_levelname(m_levelfile); std::string directory = get_level_directory(); @@ -555,7 +585,6 @@ Editor::test_level(const std::optional>& test_pos m_autosave_levelfile = FileSystem::join(directory, backup_filename); m_level->save(m_autosave_levelfile); m_time_since_last_save = 0.f; - m_leveltested = true; if (!m_level->is_worldmap()) { @@ -572,6 +601,8 @@ Editor::test_level(const std::optional>& test_pos void Editor::open_level_directory() { + if (m_temp_level) + return; m_level->save(FileSystem::join(get_level_directory(), m_levelfile)); auto path = FileSystem::join(PHYSFS_getWriteDir(), get_level_directory()); FileSystem::open_path(path); @@ -698,6 +729,8 @@ Editor::set_level(std::unique_ptr level, bool reset) { std::string sector_name = DEFAULT_SECTOR_NAME; Vector translation(0.0f, 0.0f); + + m_temp_level = (level == nullptr); if (!reset && m_sector) { translation = m_sector->get_camera().get_translation(); @@ -711,12 +744,17 @@ Editor::set_level(std::unique_ptr level, bool reset) m_toolbox_widget->get_tilebox().set_input_type(EditorTilebox::InputType::NONE); } - // Reload level. - m_level = nullptr; m_levelloaded = true; - m_level = std::move(level); - + if (level != nullptr) { + // Reload level. + m_level = std::move(level); + } + else + { + level_from_nothing(); + } + if (reset) { m_tileset = TileManager::current()->get_tileset(m_level->get_tileset()); } @@ -754,6 +792,9 @@ Editor::set_level(std::unique_ptr level, bool reset) void Editor::reload_level() { + if (m_temp_level) + return; + ReaderMapping::s_translations_enabled = false; try { @@ -828,7 +869,7 @@ Editor::quit_editor() void Editor::check_unsaved_changes(const std::function& action) { - if (!m_levelloaded) + if (!m_levelloaded || m_temp_level) { action(); return; @@ -862,6 +903,7 @@ Editor::check_unsaved_changes(const std::function& action) }); dialog->add_button(_("No"), [this, action] { action(); + set_level(nullptr, true); m_enabled = true; }); dialog->add_button(_("Cancel"), [this] { @@ -1004,6 +1046,7 @@ Editor::setup() { MenuManager::instance().push_menu(MenuStorage::EDITOR_LEVELSET_SELECT_MENU); } + set_level(nullptr, true); } m_toolbox_widget->setup(); m_layers_widget->setup(); diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index ae88525f20..8cdc712a0c 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -105,7 +105,10 @@ class Editor final : public Screen, inline int get_tileselect_move_mode() const { return m_toolbox_widget->get_tileselect_move_mode(); } inline const std::string& get_levelfile() const { return m_levelfile; } + + void level_from_nothing(); + void set_level(std::unique_ptr level, bool reset = true); inline void set_level(const std::string& levelfile) { m_levelfile = levelfile; @@ -182,7 +185,6 @@ class Editor final : public Screen, private: void set_sector(Sector* sector); - void set_level(std::unique_ptr level, bool reset = true); void reload_level(); void reset_level(); void reactivate(); @@ -199,7 +201,7 @@ class Editor final : public Screen, void keep_camera_in_bounds(); protected: - std::unique_ptr m_level; + std::shared_ptr m_level; std::unique_ptr m_world; std::string m_levelfile; @@ -231,6 +233,7 @@ class Editor final : public Screen, TileSet* m_tileset; bool m_has_deprecated_tiles; + bool m_temp_level; std::vector > m_widgets; std::vector> m_controls; diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 0d630337be..8c713ab01a 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -61,6 +61,15 @@ BaseObjectOption::BaseObjectOption(const std::string& text, const std::string& k { } +BaseObjectOption::BaseObjectOption(BaseObjectOption* other) : + m_text(other->m_text), + m_description(other->m_description), + m_key(other->m_key), + m_flags(other->m_flags), + m_last_state(other->m_last_state) +{ +} + std::string BaseObjectOption::save() const { diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index 1b1698c65d..56c45af433 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -56,6 +56,7 @@ class BaseObjectOption public: BaseObjectOption(const std::string& text, const std::string& key, unsigned int flags); + BaseObjectOption(BaseObjectOption* other); virtual ~BaseObjectOption() = default; virtual void parse(const ReaderMapping& reader) = 0; diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index e620c3812f..c314e74100 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -19,6 +19,7 @@ #include #include +#include "editor/object_option.hpp" #include "util/gettext.hpp" #include "util/log.hpp" #include "video/color.hpp" @@ -35,6 +36,16 @@ ObjectSettings::ObjectSettings(ObjectSettings&& other) : { } +ObjectSettings::ObjectSettings(ObjectSettings* obj) : + m_name(obj->m_name), + m_options() +{ + // for (auto &option : obj->m_options) + // { + // m_options.emplace_back(std::make_unique(option.get())); + // } +} + void ObjectSettings::add_option(std::unique_ptr option) { diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index 5ada65f829..d9ceb9d85b 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -44,6 +44,7 @@ class ObjectSettings final public: ObjectSettings(const std::string& name); ObjectSettings(ObjectSettings&& other); + ObjectSettings(ObjectSettings* obj); ObjectSettings& operator=(ObjectSettings&&) = default; diff --git a/src/object/spawnpoint.cpp b/src/object/spawnpoint.cpp index 2e3b9b2d0b..18d2352def 100644 --- a/src/object/spawnpoint.cpp +++ b/src/object/spawnpoint.cpp @@ -29,9 +29,7 @@ SpawnPointMarker::SpawnPointMarker(const std::string& name, const Vector& pos) : m_col.m_bbox.set_p1(pos); m_col.m_bbox.set_size(32, 32); - if (!Editor::is_active()) { - set_group(COLGROUP_DISABLED); - } + set_group(COLGROUP_DISABLED); } SpawnPointMarker::SpawnPointMarker(const ReaderMapping& mapping) : diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index f3c81fcb4e..635000f38e 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -32,6 +32,7 @@ #include "util/reader_mapping.hpp" #include "worldmap/tux.hpp" #include "worldmap/worldmap.hpp" +#include "supertux/game_session.hpp" GameManager::GameManager() : m_savegame() @@ -61,6 +62,22 @@ GameManager::start_level(const World& world, const std::string& level_filename, m_savegame->get_profile().set_last_world(world.get_basename()); } +void + + +GameManager::start_level(Level* level, + const std::optional>& start_pos) +{ + //m_current_level = std::make_unique(level); + auto screen = std::make_unique(level); + if (start_pos) + { + screen->set_start_pos(start_pos->first, start_pos->second); + } + screen->restart_level(); + ScreenManager::current()->push_screen(std::move(screen)); +} + bool GameManager::start_worldmap(const World& world, const std::string& worldmap_filename, const std::string& sector, const std::string& spawnpoint) diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 5cb4688dc3..1d28ab3ac8 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -26,6 +26,7 @@ class Savegame; class World; +class Level; class GameManager final : public Currenton { @@ -40,8 +41,10 @@ class GameManager final : public Currenton const std::optional>& start_pos); void start_level(const World& world, const std::string& level_filename, const std::optional>& start_pos = std::nullopt); + void start_level(Level* level, const std::optional>& start_pos = std::nullopt); private: + std::unique_ptr m_current_level; std::unique_ptr m_savegame; private: diff --git a/src/supertux/game_object.cpp b/src/supertux/game_object.cpp index c79f2c5959..134087e7d1 100644 --- a/src/supertux/game_object.cpp +++ b/src/supertux/game_object.cpp @@ -50,6 +50,31 @@ GameObject::GameObject(const ReaderMapping& reader) : reader.get("version", m_version, 1); } +GameObject::GameObject(GameObject* obj) : + m_parent(obj->m_parent), + m_name(obj->m_name), + m_type(obj->m_type), + m_fade_helpers(), + m_track_undo(obj->m_track_undo), + m_previous_type(obj->m_previous_type), + m_version(obj->m_version), + m_uid(obj->m_uid), + m_scheduled_for_removal(obj->m_scheduled_for_removal), + m_last_state(&*obj->m_last_state), + m_components(), + m_remove_listeners(obj->m_remove_listeners) +{ + for (auto &fade_helper : obj->m_fade_helpers) + { + m_fade_helpers.emplace_back(std::make_unique(fade_helper.get())); + } + + // for (auto &component : obj->m_components) + // { + // m_components.emplace_back(std::make_unique(component.get())); + // } +} + GameObject::~GameObject() { for (const auto& entry : m_remove_listeners) { diff --git a/src/supertux/game_object.hpp b/src/supertux/game_object.hpp index 820fd3ec7f..27e6b2a4cd 100644 --- a/src/supertux/game_object.hpp +++ b/src/supertux/game_object.hpp @@ -89,6 +89,7 @@ class GameObject : public ExposableClass public: GameObject(const std::string& name = ""); GameObject(const ReaderMapping& reader); + GameObject(GameObject* obj); virtual ~GameObject() override; /** Called after all objects have been added to the Sector and the diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index 6c1ff540c7..ea7199431f 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -57,6 +57,33 @@ GameObjectManager::GameObjectManager(bool undo_tracking) : { } +GameObjectManager::GameObjectManager(GameObjectManager* gom) : + m_initialized(gom->m_initialized), + m_uid_generator(), + m_change_uid_generator(), + m_undo_tracking(gom->m_undo_tracking), + m_undo_stack_size(gom->m_undo_stack_size), + m_undo_stack(gom->m_undo_stack), + m_redo_stack(gom->m_redo_stack), + m_pending_change_stack(gom->m_pending_change_stack), + m_last_saved_change(gom->m_last_saved_change), + m_gameobjects(), + m_gameobjects_new(), + m_moved_object_uids(gom->m_moved_object_uids), + m_solid_tilemaps(gom->m_solid_tilemaps), + m_all_tilemaps(gom->m_all_tilemaps), + m_objects_by_name(), + m_objects_by_uid(), + m_objects_by_type_index(), + m_name_resolve_requests(gom->m_name_resolve_requests) +{ + for (auto &obj : gom->m_gameobjects) + { + m_gameobjects.emplace_back(obj.get()); + } + +} + GameObjectManager::~GameObjectManager() { // clear_objects() must be called before destructing the GameObjectManager. diff --git a/src/supertux/game_object_manager.hpp b/src/supertux/game_object_manager.hpp index 0bc5267916..afa4d150f3 100644 --- a/src/supertux/game_object_manager.hpp +++ b/src/supertux/game_object_manager.hpp @@ -59,6 +59,7 @@ class GameObjectManager : public ExposableClass public: GameObjectManager(bool undo_tracking = false); + GameObjectManager(GameObjectManager* gom); virtual ~GameObjectManager() override; virtual std::string get_exposed_class_name() const override { return "GameObjectManager"; } diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index c6bc9e2ac1..d6f49053f2 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -57,18 +57,18 @@ static const int SHRINKFADE_LAYER = LAYER_LIGHTMAP - 1; static const float TELEPORT_FADE_TIME = 1.0f; -GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Statistics* statistics) : +GameSession::GameSession(Savegame* savegame, Statistics* statistics) : reset_button(false), reset_checkpoint_button(false), m_prevent_death(false), - m_level(), + m_level(nullptr), + m_level_storage(nullptr), m_statistics_backdrop(Surface::from_file("images/engine/menu/score-backdrop.png")), m_data_table(SquirrelVirtualMachine::current()->get_vm().findTable("Level").getOrCreateTable("data")), m_currentsector(nullptr), m_end_sequence(nullptr), m_game_pause(false), m_speed_before_pause(ScreenManager::current()->get_speed()), - m_levelfile(levelfile_), m_spawnpoints(), m_activated_checkpoint(), m_newsector(), @@ -78,6 +78,7 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat m_spawn_with_invincibility(false), m_best_level_statistics(statistics), m_savegame(savegame), + m_tmp_playerstatus(0), m_play_time(0), m_levelintro_shown(false), m_coins_at_start(), @@ -97,6 +98,19 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat m_data_table.clear(); } + +GameSession::GameSession(Level* level, Savegame* savegame, Statistics* statistics) : + GameSession{savegame, statistics} +{ + m_level = level; +} + +GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Statistics* statistics) : + GameSession{&savegame, statistics} +{ + m_levelfile = levelfile_; +} + void GameSession::reset_level() { @@ -111,9 +125,12 @@ GameSession::reset_level() } } - PlayerStatus& currentStatus = m_savegame.get_player_status(); - currentStatus.coins = m_coins_at_start; - currentStatus.bonus = m_boni_at_start; + if (m_savegame) + { + PlayerStatus& currentStatus = m_savegame->get_player_status(); + currentStatus.coins = m_coins_at_start; + currentStatus.bonus = m_boni_at_start; + } clear_respawn_points(); m_activated_checkpoint = nullptr; @@ -127,12 +144,22 @@ GameSession::reset_level() void GameSession::on_player_added(int id) { - auto& player_status = m_savegame.get_player_status(); - if (player_status.m_num_players <= id) - player_status.add_player(); + PlayerStatus* player_status; + if (m_savegame) + { + player_status = &m_savegame->get_player_status(); + } + else + { + player_status = &m_tmp_playerstatus; + } + + if (player_status->m_num_players <= id) + player_status->add_player(); + // ID = 0 is impossible, so no need to write `(id == 0) ? "" : ...` - auto& player = m_currentsector->add(player_status, "Tux" + std::to_string(id + 1), id); + auto& player = m_currentsector->add(*player_status, "Tux" + std::to_string(id + 1), id); player.multiplayer_prepare_spawn(); } @@ -158,23 +185,26 @@ GameSession::on_player_removed(int id) void GameSession::restart_level(bool after_death, bool preserve_music) { - const PlayerStatus& currentStatus = m_savegame.get_player_status(); - m_coins_at_start = currentStatus.coins; - m_boni_at_start = currentStatus.bonus; - - // Needed for the title screen apparently. - if (m_currentsector) + if (m_savegame) { - try + const PlayerStatus& currentStatus = m_savegame->get_player_status(); + m_coins_at_start = currentStatus.coins; + m_boni_at_start = currentStatus.bonus; + + // Needed for the title screen apparently. + if (m_currentsector) { - for (const auto& p : m_currentsector->get_players()) + try + { + for (const auto& p : m_currentsector->get_players()) + { + p->set_bonus(m_boni_at_start.at(p->get_id()), false); + m_boni_at_start[p->get_id()] = currentStatus.bonus[p->get_id()]; + } + } + catch (const std::out_of_range&) { - p->set_bonus(m_boni_at_start.at(p->get_id()), false); - m_boni_at_start[p->get_id()] = currentStatus.bonus[p->get_id()]; } - } - catch (const std::out_of_range&) - { } } @@ -187,13 +217,17 @@ GameSession::restart_level(bool after_death, bool preserve_music) m_currentsector = nullptr; - const std::string base_dir = FileSystem::dirname(m_levelfile); - if (base_dir == "./") { - m_levelfile = FileSystem::basename(m_levelfile); - } - try { - m_level = LevelParser::from_file(m_levelfile, false, false); + if (FileSystem::dirname(m_levelfile) == "./") { + m_levelfile = FileSystem::basename(m_levelfile); + } + + // Level was passed as an argument (likely from the editor) + if (m_level == nullptr && !m_levelfile.empty()) + { + m_level_storage = LevelParser::from_file(m_levelfile, false, false); + m_level = m_level_storage.get(); + } /* Determine the spawnpoint to spawn/respawn Tux to. */ const GameSession::SpawnPoint* spawnpoint = nullptr; @@ -252,6 +286,8 @@ GameSession::restart_level(bool after_death, bool preserve_music) } catch (std::exception& e) { throw std::runtime_error(std::string("Couldn't start level: ") + e.what()); + // ScreenManager::current()->pop_screen(); + //return (-1); } if (m_levelintro_shown) @@ -409,8 +445,11 @@ GameSession::abort_level() } } - PlayerStatus& currentStatus = m_savegame.get_player_status(); - currentStatus.coins = m_coins_at_start; + if (m_savegame) + { + PlayerStatus& currentStatus = m_savegame->get_player_status(); + currentStatus.coins = m_coins_at_start; + } SoundManager::current()->stop_sounds(); } @@ -423,6 +462,8 @@ GameSession::is_active() const void GameSession::check_end_conditions() { + if (!m_currentsector) + return; bool all_dead = true; for (const auto* p : m_currentsector->get_players()) if (!(all_dead &= p->is_dead())) @@ -483,10 +524,10 @@ GameSession::setup() m_currentsector->get_singleton_by_type().play_music(LEVEL_MUSIC); int total_stats_to_be_collected = m_level->m_stats.m_total_coins + m_level->m_stats.m_total_badguys + m_level->m_stats.m_total_secrets; - if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0)) { + if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0) && m_level_storage != nullptr) { m_levelintro_shown = true; m_active = false; - ScreenManager::current()->push_screen(std::make_unique(*m_level, m_best_level_statistics, m_savegame.get_player_status())); + ScreenManager::current()->push_screen(std::make_unique(*m_level, m_best_level_statistics, m_savegame->get_player_status())); ScreenManager::current()->set_screen_fade(std::make_unique(FadeToBlack::FADEIN, TELEPORT_FADE_TIME)); } else @@ -630,7 +671,8 @@ GameSession::update(float dt_sec, const Controller& controller) { if (player->is_active() && player->is_scripting_activated() && player->get_controller().pressed(Control::ITEM) && - m_savegame.get_player_status().m_item_pockets.size() > 0) + m_savegame && + m_savegame->get_player_status().m_item_pockets.size() > 0) { player->get_status().give_item_from_pocket(player); } @@ -725,7 +767,7 @@ GameSession::finish(bool win) if (win) { if (WorldMapSector::current()) { - WorldMapSector::current()->finished_level(m_level.get()); + WorldMapSector::current()->finished_level(m_level); } if (LevelsetScreen::current()) diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index 55e8ccac55..868a2d8297 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -34,12 +34,12 @@ #include "supertux/screen_fade.hpp" #include "supertux/sequence.hpp" #include "supertux/timer.hpp" +#include "supertux/level.hpp" #include "video/surface_ptr.hpp" class CodeController; class DrawingContext; class EndSequence; -class Level; class Player; class Sector; class Statistics; @@ -77,6 +77,8 @@ class GameSession final : public Screen, }; public: + GameSession(Savegame* savegame = nullptr, Statistics* statistics = nullptr); + GameSession(Level* level, Savegame* savegame = nullptr, Statistics* statistics = nullptr); GameSession(const std::string& levelfile, Savegame& savegame, Statistics* statistics = nullptr); virtual void draw(Compositor& compositor) override; @@ -135,7 +137,7 @@ class GameSession final : public Screen, void abort_level(); bool is_active() const; - inline Savegame& get_savegame() const { return m_savegame; } + inline Savegame& get_savegame() const { return *m_savegame; } void set_scheduler(SquirrelScheduler& new_scheduler); @@ -156,7 +158,8 @@ class GameSession final : public Screen, bool m_prevent_death; /**< true if players should enter ghost mode instead of dying */ private: - std::unique_ptr m_level; + Level* m_level; + std::unique_ptr m_level_storage; SurfacePtr m_statistics_backdrop; ssq::Table m_data_table; @@ -182,7 +185,9 @@ class GameSession final : public Screen, bool m_spawn_with_invincibility; Statistics* m_best_level_statistics; - Savegame& m_savegame; + Savegame* m_savegame; + + PlayerStatus m_tmp_playerstatus; // Note: m_play_time should reset when a level is restarted from the beginning // but NOT if Tux respawns at a checkpoint (for LevelTimes to work) diff --git a/src/supertux/level.cpp b/src/supertux/level.cpp index eff00afaf0..ca39dfe8b4 100644 --- a/src/supertux/level.cpp +++ b/src/supertux/level.cpp @@ -65,6 +65,35 @@ Level::Level(bool worldmap) : } } +Level::Level(Level* level) : + m_is_worldmap(level->m_is_worldmap), + m_name(level->m_name), + m_author(level->m_author), + m_contact(level->m_contact), + m_license(level->m_license), + m_filename(level->m_filename), + m_note(level->m_note), + m_sectors(), + m_stats(), + m_target_time(level->m_target_time), + m_tileset(level->m_tileset), + m_allow_item_pocket(level->m_allow_item_pocket), + m_suppress_pause_menu(level->m_suppress_pause_menu), + m_is_in_cutscene(level->m_is_in_cutscene), + m_skip_cutscene(level->m_skip_cutscene), + m_icon(level->m_icon), + m_icon_locked(level->m_icon_locked), + m_wmselect_bkg(level->m_wmselect_bkg), + m_saving_in_progress(level->m_saving_in_progress) +{ + s_current = this; + + for (auto §ors : level->m_sectors) + { + m_sectors.push_back(std::make_unique(sectors.get())); + } +} + Level::~Level() { m_sectors.clear(); diff --git a/src/supertux/level.hpp b/src/supertux/level.hpp index 9caf51ce73..368b7e4d5c 100644 --- a/src/supertux/level.hpp +++ b/src/supertux/level.hpp @@ -39,6 +39,7 @@ class Level final public: explicit Level(bool m_is_worldmap); + Level(Level* level); ~Level(); // saves to a levelfile @@ -72,8 +73,9 @@ class Level final inline const std::string& get_license() const { return m_license; } -private: void initialize(); + +private: void load_old_format(const ReaderMapping& reader); public: diff --git a/src/supertux/menu/editor_levelset_select_menu.cpp b/src/supertux/menu/editor_levelset_select_menu.cpp index b3a18a1f29..ad2c89c02e 100644 --- a/src/supertux/menu/editor_levelset_select_menu.cpp +++ b/src/supertux/menu/editor_levelset_select_menu.cpp @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "supertux/constants.hpp" #include "supertux/menu/editor_level_select_menu.hpp" #include @@ -25,9 +26,11 @@ #include "gui/menu_manager.hpp" #include "physfs/util.hpp" #include "supertux/levelset.hpp" +#include "supertux/level.hpp" #include "supertux/menu/editor_levelset_select_menu.hpp" #include "supertux/menu/editor_delete_levelset_menu.hpp" #include "supertux/menu/menu_storage.hpp" +#include "supertux/sector_parser.hpp" #include "supertux/world.hpp" #include "util/file_system.hpp" #include "util/gettext.hpp" diff --git a/src/supertux/sector.cpp b/src/supertux/sector.cpp index 7363087a9c..b032f67f1f 100644 --- a/src/supertux/sector.cpp +++ b/src/supertux/sector.cpp @@ -77,6 +77,19 @@ Sector::Sector(Level& parent) : SoundManager::current()->preload("sounds/shoot.wav"); } +Sector::Sector(Sector* sector) : + Base::Sector(sector), + m_level(sector->m_level), + m_text_object(add("Text")), + m_foremost_layer(sector->m_foremost_layer), + m_foremost_opaque_layer(sector->m_foremost_opaque_layer), + m_gravity(sector->m_gravity), + m_collision_system(sector->m_collision_system.get()) +{ + +} + + Sector::~Sector() { try @@ -235,9 +248,6 @@ Sector::activate(const Vector& player_pos) // The Sector object is called 'settings' as it is accessed as 'sector.settings' m_squirrel_environment->expose(*this, "settings"); - if (Editor::is_active()) - return; - // two-player hack: move other players to main player's position // Maybe specify 2 spawnpoints in the level? const auto players = get_objects_by_type(); diff --git a/src/supertux/sector.hpp b/src/supertux/sector.hpp index ff41407b09..dda66bbe8e 100644 --- a/src/supertux/sector.hpp +++ b/src/supertux/sector.hpp @@ -75,6 +75,7 @@ class Sector final : public Base::Sector public: Sector(Level& parent); + Sector(Sector* sector); ~Sector() override; void finish_construction(bool editable) override; diff --git a/src/supertux/sector_base.cpp b/src/supertux/sector_base.cpp index 394e9ef182..1ccb702fbc 100644 --- a/src/supertux/sector_base.cpp +++ b/src/supertux/sector_base.cpp @@ -28,6 +28,14 @@ Sector::Sector(const std::string& type) : { } +Sector::Sector(Sector* sector) : + GameObjectManager(sector), + m_name(sector->m_name), + m_init_script(sector->m_init_script), + m_squirrel_environment(sector->m_squirrel_environment) +{ +} + void Sector::finish_construction(bool) { diff --git a/src/supertux/sector_base.hpp b/src/supertux/sector_base.hpp index 6fb2d776e6..b26694ab71 100644 --- a/src/supertux/sector_base.hpp +++ b/src/supertux/sector_base.hpp @@ -30,6 +30,7 @@ class Sector : public GameObjectManager { public: Sector(const std::string& type); + Sector(Sector* sector); /** Needs to be called after parsing to finish the construction of the Sector before using it. */ @@ -55,7 +56,7 @@ class Sector : public GameObjectManager std::string m_name; std::string m_init_script; - std::unique_ptr m_squirrel_environment; + std::shared_ptr m_squirrel_environment; private: Sector(const Sector&) = delete; diff --git a/src/util/fade_helper.cpp b/src/util/fade_helper.cpp index 59f0ee0c71..3cc6d8a3d8 100644 --- a/src/util/fade_helper.cpp +++ b/src/util/fade_helper.cpp @@ -40,6 +40,17 @@ FadeHelper::FadeHelper(float* value, float time, { } +FadeHelper::FadeHelper(FadeHelper* other) : + m_value(other->m_value), + m_progress(other->m_progress), + m_start(other->m_start), + m_target(other->m_target), + m_time(other->m_time), + m_total_time(other->m_total_time), + m_ease(other->m_ease) +{ +} + float FadeHelper::update(float dt_sec) { diff --git a/src/util/fade_helper.hpp b/src/util/fade_helper.hpp index cf87d4e9de..6496450bc5 100644 --- a/src/util/fade_helper.hpp +++ b/src/util/fade_helper.hpp @@ -29,7 +29,8 @@ class FadeHelper FadeHelper(float* value, float time, float target_value, easing ease = LinearInterpolation); - + FadeHelper(FadeHelper* other); + /** * Increases the internal timer of the FadeHelper. * From 79b398c30e6d1025e959a4bf371c363d003f2e79 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 18:20:08 -0400 Subject: [PATCH 043/141] Warn user about testing an unsaved level until I fix that --- src/editor/editor.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index cf116ff3f4..91ae3eef21 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -562,6 +562,16 @@ Editor::test_level(const std::optional>& test_pos { Tile::draw_editor_images = false; Compositor::s_render_lighting = true; + + // Until I get testing to not clobber the editor level, display a message. + if (m_temp_level) + { + std::string message = _("You cannot test an unsaved level at the moment.\n\n" + "Please save your level before testing."); + + Dialog::show_message(message); + return; + } m_leveltested = true; if ((m_level && m_levelfile.empty()) || m_levelfile == "") @@ -792,9 +802,6 @@ Editor::set_level(std::unique_ptr level, bool reset) void Editor::reload_level() { - if (m_temp_level) - return; - ReaderMapping::s_translations_enabled = false; try { From 9b3acfa760bbd473d181bc0d5dea9cf540867873 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 18:28:55 -0400 Subject: [PATCH 044/141] Temporarily reload level for now --- src/supertux/game_session.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index d6f49053f2..5c54184bd4 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -223,7 +223,8 @@ GameSession::restart_level(bool after_death, bool preserve_music) } // Level was passed as an argument (likely from the editor) - if (m_level == nullptr && !m_levelfile.empty()) + if (true || //temporary + m_level == nullptr && !m_levelfile.empty()) { m_level_storage = LevelParser::from_file(m_levelfile, false, false); m_level = m_level_storage.get(); From 9fbc955ef2d5b1364452ff0ee5ce3d8808096f73 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 18:46:23 -0400 Subject: [PATCH 045/141] Fix size of undo/redo buttons --- src/editor/button_widget.cpp | 7 ++++--- src/editor/button_widget.hpp | 14 +++++++------- src/editor/editor.cpp | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index fc5384fc46..f60400c580 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -24,10 +24,11 @@ #include "video/video_system.hpp" ButtonWidget::ButtonWidget(SpritePtr sprite, const Vector& pos, - std::function sig_click) : + std::function sig_click, std::optional sprite_size) : m_sprite(std::move(sprite)), - m_rect(pos, Sizef(static_cast(m_sprite->get_width()), - static_cast(m_sprite->get_height()))), + m_rect(pos, sprite_size ? *sprite_size : + Sizef(static_cast(m_sprite->get_width()), + static_cast(m_sprite->get_height()))), m_grab(false), m_hover(false), m_sig_click(std::move(sig_click)), diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index acd96b1aba..875908e451 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -28,9 +28,9 @@ class ButtonWidget : public Widget { private: public: - ButtonWidget(SpritePtr sprite, const Vector& pos, std::function m_sig_click = {}); - ButtonWidget(const std::string& path, const Vector& pos, std::function callback = {}) : - ButtonWidget(SpriteManager::current()->create(path), pos, std::move(callback)) + ButtonWidget(SpritePtr sprite, const Vector& pos, std::function m_sig_click = {}, std::optional sprite_size = std::nullopt); + ButtonWidget(const std::string& path, const Vector& pos, std::function callback = {}, std::optional sprite_size = std::nullopt) : + ButtonWidget(SpriteManager::current()->create(path), std::move(pos), std::move(callback), std::move(sprite_size)) { } @@ -66,16 +66,16 @@ class ButtonWidget : public Widget class EditorToolbarButtonWidget : public ButtonWidget { public: - EditorToolbarButtonWidget(SpritePtr sprite, const Vector& pos, std::function m_sig_click = {}) : - ButtonWidget(std::move(sprite), pos, m_sig_click), + EditorToolbarButtonWidget(SpritePtr sprite, const Vector& pos, std::function m_sig_click = {}, std::optional sprite_size = std::nullopt) : + ButtonWidget(std::move(sprite), std::move(pos), m_sig_click, std::move(sprite_size)), m_tile_mode_visible(true), m_object_mode_visible(true), m_visible(true) { } - EditorToolbarButtonWidget(const std::string& path, const Vector& pos, std::function callback = {}) : - ButtonWidget(SpriteManager::current()->create(path), pos, std::move(callback)), + EditorToolbarButtonWidget(const std::string& path, const Vector& pos, std::function callback = {}, std::optional sprite_size = std::nullopt) : + ButtonWidget(SpriteManager::current()->create(path), std::move(pos), std::move(callback), std::move(sprite_size)), m_tile_mode_visible(true), m_object_mode_visible(true), m_visible(true) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 91ae3eef21..8396a0ef97 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1287,10 +1287,10 @@ Editor::retoggle_undo_tracking() { // Add undo/redo button widgets. auto undo_button_widget = std::make_unique("images/engine/editor/undo.png", - Vector(0, 0), [this]{ undo(); }); + Vector(0, 0), [this]{ undo(); }, Sizef(32.f, 32.f)); undo_button_widget->set_help_text(_("Undo")); auto redo_button_widget = std::make_unique("images/engine/editor/redo.png", - Vector(32, 0), [this]{ redo(); }); + Vector(32, 0), [this]{ redo(); }, Sizef(32.f, 32.f)); redo_button_widget->set_help_text(_("Redo")); m_undo_widget = undo_button_widget.get(); From 5be2ea2e7ef0e8e7b47ccc3aed6a3851b286fd01 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 19:30:55 -0400 Subject: [PATCH 046/141] Camera overscroll; Tilemap outer shading --- src/editor/editor.cpp | 33 +++++++++++++++++++++------------ src/editor/overlay_widget.cpp | 31 ++++++++++++++++++++++++++++--- src/editor/overlay_widget.hpp | 3 ++- src/math/rectf.hpp | 4 ---- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 8396a0ef97..4aaccdb340 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -334,17 +334,23 @@ Editor::draw(Compositor& compositor) for(const auto& widget : m_widgets) { widget->draw(context); } + + m_overlay_widget->draw_tilemap_outer_shading(context); + m_overlay_widget->draw_tilemap_border(context); + + if (m_controls.size() != 0) + { + context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), + Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); - context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), - Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 6); - - context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), - Color(0.2f, 0.2f, 0.2f), LAYER_GUI - 6); + context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), + Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); - for(const auto& control : m_controls) - { - control->draw(context); - } + for(const auto& control : m_controls) + { + control->draw(context); + } + } // If camera scale must be changed, change it here. if (m_new_scale != 0.f) @@ -631,9 +637,12 @@ void Editor::keep_camera_in_bounds() { Camera& camera = m_sector->get_camera(); - camera.keep_in_bounds(Rectf(-200.f, -32.0f, - std::max(0.0f, m_sector->get_editor_width() + 128.f / camera.get_current_scale()), - std::max(0.0f, m_sector->get_editor_height() + 32.f / camera.get_current_scale()))); + constexpr float offset = 80.f; + float controls_offset_x = m_controls.size() != 0 ? -200.f : 0.f; + float controls_offset_y = m_controls.size() != 0 ? -32.f : 0.f; + camera.keep_in_bounds(Rectf(-offset + controls_offset_x, -offset + controls_offset_y, + std::max(0.0f, m_sector->get_editor_width() + 128.f / camera.get_current_scale()) + offset, + std::max(0.0f, m_sector->get_editor_height() + 32.f / camera.get_current_scale()) + offset)); m_overlay_widget->update_pos(); } diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 1546e21e76..b5ac094ffd 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -1567,8 +1567,6 @@ EditorOverlayWidget::draw_tile_grid(DrawingContext& context, int tile_size, bool void EditorOverlayWidget::draw_tilemap_border(DrawingContext& context) { - if (!m_editor.get_selected_tilemap()) return; - auto current_tm = m_editor.get_selected_tilemap(); if (!current_tm) return; @@ -1581,6 +1579,34 @@ EditorOverlayWidget::draw_tilemap_border(DrawingContext& context) context.color().draw_line(Vector(end.x, start.y), end, Color(1, 0, 1), current_tm->get_layer()); } +void +EditorOverlayWidget::draw_tilemap_outer_shading(DrawingContext& context) +{ + auto current_tm = m_editor.get_selected_tilemap(); + if (!current_tm) return; + + Vector start = tile_screen_pos( Vector(0, 0) ); + Vector end = tile_screen_pos( Vector(static_cast(current_tm->get_width()), + static_cast(current_tm->get_height())) ); + + const Color& bg_color = { 0, 0, 0, 0.15 }; + const Camera& camera = m_editor.get_sector()->get_camera(); + float w_l = (-camera.get_x()) * camera.get_current_scale(); + float height = (camera.get_screen_height()) * camera.get_current_scale(); + float w_r = (current_tm->get_width() - camera.get_x()) * camera.get_current_scale(); + // Left + context.color().draw_filled_rect({0,0,start.x,height}, bg_color, current_tm->get_layer()); + // Top + context.color().draw_filled_rect({start.x, 0, end.x, start.y}, bg_color, current_tm->get_layer()); + // Right + context.color().draw_filled_rect( + {end.x, 0, + static_cast(context.get_viewport().get_right()), height}, + bg_color, current_tm->get_layer()); + // Bottom + context.color().draw_filled_rect({start.x, end.y, end.x, height}, bg_color, current_tm->get_layer()); +} + void EditorOverlayWidget::draw_path(DrawingContext& context) { @@ -1643,7 +1669,6 @@ EditorOverlayWidget::draw(DrawingContext& context) if (g_config->editor_render_grid) { draw_tile_grid(context, 32, true); - draw_tilemap_border(context); auto snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; if (snap_grid_size != 32) { diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index feb117b619..c7f1ec048f 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -73,6 +73,8 @@ class EditorOverlayWidget final : public Widget void edit_path(PathGameObject* path, GameObject* new_marked_object = nullptr); //void reset_action_press(); + void draw_tilemap_outer_shading(DrawingContext&); + void draw_tilemap_border(DrawingContext&); private: static bool action_pressed; static bool alt_pressed; @@ -106,7 +108,6 @@ class EditorOverlayWidget final : public Widget void draw_tile_tip(DrawingContext&); void draw_tile_grid(DrawingContext&, int tile_size, bool draw_shadow) const; - void draw_tilemap_border(DrawingContext&); void draw_path(DrawingContext&); void draw_rectangle_preview(DrawingContext& context); diff --git a/src/math/rectf.hpp b/src/math/rectf.hpp index 8a5af8b88e..09d2b6faa0 100644 --- a/src/math/rectf.hpp +++ b/src/math/rectf.hpp @@ -50,15 +50,11 @@ class Rectf final Rectf(const Vector& np1, const Vector& np2) : m_p1(np1), m_size(np2.x - np1.x, np2.y - np1.y) { - assert(m_size.width >= 0 && - m_size.height >= 0); } Rectf(float x1, float y1, float x2, float y2) : m_p1(x1, y1), m_size(x2 - x1, y2 - y1) { - assert(m_size.width >= 0 && - m_size.height >= 0); } Rectf(const Vector& p1, const Sizef& size) : From a2d958f63cbf44feb54f3bf7ceba433d6d428fdf Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 21:17:35 -0400 Subject: [PATCH 047/141] Ctrl-x to toggle between tile/object --- src/editor/editor.cpp | 61 +++++++++++++++++++++++++------------------ src/editor/editor.hpp | 2 ++ 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 4aaccdb340..03312c9858 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -192,31 +192,9 @@ Editor::Editor() : auto mode_button = std::make_unique("images/engine/editor/toggle_tile_object_mode.png", Vector(160, 0), [this] { - auto& tilebox = m_toolbox_widget->get_tilebox(); - const auto& input_type = tilebox.get_input_type(); - if (input_type == InputType::OBJECT) - { - select_tilegroup(0); - for(const auto& widget : m_widgets) - { - if (auto toolbar_button = dynamic_cast(widget.get())) - { - toolbar_button->set_visible(toolbar_button->get_visible_in_tile_mode()); - } - } - } - else - { - select_objectgroup(0); - for(const auto& widget : m_widgets) - { - if (auto toolbar_button = dynamic_cast(widget.get())) - { - toolbar_button->set_visible(toolbar_button->get_visible_in_object_mode()); - } - } - } - }); + toggle_tile_object_mode(); + } + ); mode_button->set_help_text(_("Toggle between object and tile mode")); m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); @@ -1142,6 +1120,9 @@ Editor::event(const SDL_Event& ev) case SDLK_y: redo(); break; + case SDLK_x: + toggle_tile_object_mode(); + break; case SDLK_PLUS: // Zoom in case SDLK_KP_PLUS: m_new_scale = m_sector->get_camera().get_current_scale() + CAMERA_ZOOM_SENSITIVITY; @@ -1188,6 +1169,36 @@ Editor::event(const SDL_Event& ev) } } +void +Editor::toggle_tile_object_mode() +{ + auto& tilebox = m_toolbox_widget->get_tilebox(); + const auto& input_type = tilebox.get_input_type(); + if (input_type == InputType::OBJECT) + { + select_tilegroup(0); + for(const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + toolbar_button->set_visible(toolbar_button->get_visible_in_tile_mode()); + } + } + } + else + { + select_objectgroup(0); + for(const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + toolbar_button->set_visible(toolbar_button->get_visible_in_object_mode()); + } + } + } +} + + void Editor::update_node_iterators() { diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 8cdc712a0c..884f5d2cb9 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -96,6 +96,8 @@ class Editor final : public Screen, inline TileSet* get_tileset() const { return m_tileset; } inline TileSelection* get_tiles() const { return m_toolbox_widget->get_tilebox().get_tiles(); } inline std::string get_tileselect_object() const { return m_toolbox_widget->get_tilebox().get_object(); } + + void toggle_tile_object_mode(); inline EditorTilebox::InputType get_tileselect_input_type() const { return m_toolbox_widget->get_tilebox().get_input_type(); } From ad619b082e0bc3ccbf2d38c01a7fdcbdf279bc49 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 21:26:35 -0400 Subject: [PATCH 048/141] Shift+scroll to scroll horizontally --- src/editor/editor.cpp | 6 +++++- src/editor/editor.hpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 03312c9858..d1e51bc7dc 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1093,6 +1093,7 @@ Editor::event(const SDL_Event& ev) if (ev.type == SDL_KEYDOWN) { m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; + m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; if (m_ctrl_pressed) m_scroll_speed = 16.0f; @@ -1140,6 +1141,7 @@ Editor::event(const SDL_Event& ev) else if (ev.type == SDL_KEYUP) { m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; + m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; if (!m_ctrl_pressed && !(ev.key.keysym.mod & KMOD_RSHIFT)) m_scroll_speed = 32.0f; @@ -1154,8 +1156,10 @@ Editor::event(const SDL_Event& ev) // The toolbox does scrolling independently from the main area. if (m_ctrl_pressed) m_new_scale = m_sector->get_camera().get_current_scale() + static_cast(ev.wheel.y) * CAMERA_ZOOM_SENSITIVITY; + else if (m_shift_pressed) + scroll({ static_cast(ev.wheel.y * -40), static_cast(ev.wheel.x * -40) }); else - scroll({ static_cast(ev.wheel.x * -32), static_cast(ev.wheel.y * -32) }); + scroll({ static_cast(ev.wheel.x * -40), static_cast(ev.wheel.y * -40) }); } BIND_SECTOR(*m_sector); diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 884f5d2cb9..fbb0440309 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -225,6 +225,7 @@ class Editor final : public Screen, std::string* m_particle_editor_filename; bool m_ctrl_pressed; + bool m_shift_pressed; private: Sector* m_sector; From d5260abb4834a5d4af492e9ebdafa11dd254ce30 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 21:42:29 -0400 Subject: [PATCH 049/141] Remember last used tile-group/object-group --- src/editor/editor.cpp | 16 ++++++++++++++-- src/editor/editor.hpp | 2 ++ src/editor/tilebox.cpp | 17 +++++++++++++++++ src/editor/tilebox.hpp | 4 ++++ src/editor/toolbox_widget.cpp | 12 ++++++++++++ src/editor/toolbox_widget.hpp | 2 ++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index d1e51bc7dc..388da7ba56 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1180,7 +1180,7 @@ Editor::toggle_tile_object_mode() const auto& input_type = tilebox.get_input_type(); if (input_type == InputType::OBJECT) { - select_tilegroup(0); + select_last_tilegroup(); for(const auto& widget : m_widgets) { if (auto toolbar_button = dynamic_cast(widget.get())) @@ -1191,7 +1191,7 @@ Editor::toggle_tile_object_mode() } else { - select_objectgroup(0); + select_last_objectgroup(); for(const auto& widget : m_widgets) { if (auto toolbar_button = dynamic_cast(widget.get())) @@ -1227,6 +1227,12 @@ Editor::select_tilegroup(int id) m_toolbox_widget->select_tilegroup(id); } +void +Editor::select_last_tilegroup() +{ + m_toolbox_widget->select_last_tilegroup(); +} + const std::vector& Editor::get_tilegroups() const { @@ -1251,6 +1257,12 @@ Editor::select_objectgroup(int id) m_toolbox_widget->select_objectgroup(id); } +void +Editor::select_last_objectgroup() +{ + m_toolbox_widget->select_last_objectgroup(); +} + const std::vector& Editor::get_objectgroups() const { diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index fbb0440309..e422416da4 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -148,10 +148,12 @@ class Editor final : public Screen, void sort_layers(); void select_tilegroup(int id); + void select_last_tilegroup(); const std::vector& get_tilegroups() const; void change_tileset(); void select_objectgroup(int id); + void select_last_objectgroup(); const std::vector& get_objectgroups() const; void scroll(const Vector& velocity); diff --git a/src/editor/tilebox.cpp b/src/editor/tilebox.cpp index f6d071e26a..62cc4481d4 100644 --- a/src/editor/tilebox.cpp +++ b/src/editor/tilebox.cpp @@ -36,6 +36,8 @@ EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : m_rect(rect), m_tiles(new TileSelection()), m_object(), + m_tilegroup_id(0), + m_objectgroup_id(0), m_object_tip(new Tip()), m_input_type(InputType::NONE), m_active_tilegroup(), @@ -373,18 +375,33 @@ void EditorTilebox::select_tilegroup(int id) { m_active_tilegroup.reset(new Tilegroup(m_editor.get_tileset()->get_tilegroups()[id])); + m_tilegroup_id = id; m_input_type = InputType::TILE; reset_scrollbar(); } +void +EditorTilebox::select_last_tilegroup() +{ + select_tilegroup(get_tilegroup_id()); +} + void EditorTilebox::select_objectgroup(int id) { m_active_objectgroup = &m_object_info->m_groups[id]; + m_objectgroup_id = id; m_input_type = InputType::OBJECT; reset_scrollbar(); } +void +EditorTilebox::select_last_objectgroup() +{ + select_objectgroup(m_objectgroup_id); +} + + bool EditorTilebox::select_layers_objectgroup() { diff --git a/src/editor/tilebox.hpp b/src/editor/tilebox.hpp index c76e4a324e..761251806d 100644 --- a/src/editor/tilebox.hpp +++ b/src/editor/tilebox.hpp @@ -74,8 +74,10 @@ class EditorTilebox final : public Widget void on_select(const std::function& callback); void select_tilegroup(int id); + void select_last_tilegroup(); inline void set_tilegroup(std::unique_ptr tilegroup) { m_active_tilegroup = std::move(tilegroup); } void select_objectgroup(int id); + void select_last_objectgroup(); bool select_layers_objectgroup(); inline const ObjectInfo& get_object_info() const { return *m_object_info; } @@ -89,6 +91,7 @@ class EditorTilebox final : public Widget float get_tiles_height() const; inline bool has_active_object_tip() const { return m_object_tip->get_visible(); } + inline size_t get_tilegroup_id() const { return m_tilegroup_id; } private: Vector get_tile_coords(int pos, bool relative = true) const; @@ -120,6 +123,7 @@ class EditorTilebox final : public Widget std::unique_ptr m_active_tilegroup; ObjectGroup* m_active_objectgroup; std::unique_ptr m_object_info; + size_t m_tilegroup_id, m_objectgroup_id; std::function m_on_select_callback; diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index d7d8d91781..54b5a62c5f 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -306,6 +306,18 @@ EditorToolboxWidget::select_objectgroup(int id) update_mouse_icon(); } +void +EditorToolboxWidget::select_last_tilegroup() +{ + m_tilebox->select_last_tilegroup(); +} + +void +EditorToolboxWidget::select_last_objectgroup() +{ + m_tilebox->select_last_objectgroup(); +} + int EditorToolboxWidget::get_tileselect_select_mode() const { diff --git a/src/editor/toolbox_widget.hpp b/src/editor/toolbox_widget.hpp index f9192194f2..648df4e629 100644 --- a/src/editor/toolbox_widget.hpp +++ b/src/editor/toolbox_widget.hpp @@ -54,6 +54,8 @@ class EditorToolboxWidget final : public Widget void select_tilegroup(int id); void select_objectgroup(int id); + void select_last_tilegroup(); + void select_last_objectgroup(); int get_tileselect_select_mode() const; int get_tileselect_move_mode() const; From a010c0dd104d26d63ca30c1cdfbe4804e45ddf0e Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 22:28:33 -0400 Subject: [PATCH 050/141] Scroll mouse wheel to select item in menu This was a feature I really liked from a window manager called Enlightenment. --- src/gui/menu.cpp | 65 ++++++++++++++++++++++++++++++++++++++---------- src/gui/menu.hpp | 4 +++ 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index cf14843af5..56113623f0 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -55,6 +55,9 @@ #include "supertux/error_handler.hpp" +// The amount in pixels the mouse has to wiggle after scrolling before it can hover over things again. +constexpr int MOUSE_DEADZONE_AMOUNT = 70; + Menu::Menu() : m_pos(Vector(static_cast(SCREEN_WIDTH) / 2.0f, static_cast(SCREEN_HEIGHT) / 2.0f)), @@ -65,7 +68,8 @@ Menu::Menu() : m_menu_help_height(0.0f), m_items(), m_arrange_left(0), - m_active_item(-1) + m_active_item(-1), + m_mouse_deadzone(0) { } @@ -327,6 +331,24 @@ Menu::clear() m_active_item = -1; } +void +Menu::previous_item() +{ + if (m_active_item > 0) + --m_active_item; + else + m_active_item = m_items.size() - 1; +} + +void +Menu::next_item() +{ + if (m_active_item < m_items.size() - 1 ) + ++m_active_item; + else + m_active_item = 0; +} + void Menu::process_action(const MenuAction& action) { @@ -370,20 +392,14 @@ Menu::process_action(const MenuAction& action) switch (action) { case MenuAction::UP: do { - if (m_active_item > 0) - --m_active_item; - else - m_active_item = int(m_items.size())-1; + previous_item(); } while (m_items[m_active_item]->skippable() && (m_active_item != last_active_item)); break; case MenuAction::DOWN: do { - if (m_active_item < int(m_items.size())-1 ) - ++m_active_item; - else - m_active_item = 0; + next_item(); } while (m_items[m_active_item]->skippable() && (m_active_item != last_active_item)); break; @@ -590,16 +606,30 @@ Menu::event(const SDL_Event& ev) calculate_width(); } break; + + case SDL_MOUSEWHEEL: + { + if (ev.wheel.y > 0) + { + do { previous_item(); } while (m_items[m_active_item]->skippable()); + } + else { + do { next_item(); } while (m_items[m_active_item]->skippable()); + } + m_mouse_deadzone = MOUSE_DEADZONE_AMOUNT; + } + break; case SDL_MOUSEBUTTONDOWN: if (ev.button.button == SDL_BUTTON_LEFT) { Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y); - if (mouse_pos.x > m_pos.x - get_width() / 2.0f && - mouse_pos.x < m_pos.x + get_width() / 2.0f && - mouse_pos.y > m_pos.y - get_height() / 2.0f && - mouse_pos.y < m_pos.y + get_height() / 2.0f) + if ((mouse_pos.x > m_pos.x - get_width() / 2.0f && + mouse_pos.x < m_pos.x + get_width() / 2.0f && + mouse_pos.y > m_pos.y - get_height() / 2.0f && + mouse_pos.y < m_pos.y + get_height() / 2.0f) || + m_mouse_deadzone > 0) { process_action(MenuAction::HIT); } @@ -608,6 +638,15 @@ Menu::event(const SDL_Event& ev) case SDL_MOUSEMOTION: { + if (m_mouse_deadzone > 0) + { + m_mouse_deadzone -= abs(ev.motion.xrel); + m_mouse_deadzone -= abs(ev.motion.yrel); + + if (m_mouse_deadzone < 0) + m_mouse_deadzone = 0; + return; + } Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y); float x = mouse_pos.x; float y = mouse_pos.y; diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index 4d7ff1e4a7..22a62b05b9 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -122,6 +122,9 @@ class Menu inline Vector get_center_pos() const { return m_pos; } inline void set_center_pos(float x, float y) { m_pos.x = x; m_pos.y = y; } + + void previous_item(); + void next_item(); float get_width() const; float get_height() const; @@ -161,6 +164,7 @@ class Menu float m_menu_width; float m_menu_height; float m_menu_help_height; + int m_mouse_deadzone; public: std::vector > m_items; From 9e3b3470fe5c7fba62e065a5f5ae5423b8899c4c Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 22:37:49 -0400 Subject: [PATCH 051/141] Initialize m_shift_pressed --- src/editor/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 388da7ba56..e883445894 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -117,6 +117,7 @@ Editor::Editor() : m_temp_level(true), m_particle_editor_filename(), m_ctrl_pressed(false), + m_shift_pressed(false), m_sector(), m_levelloaded(false), m_leveltested(false), From 1e22ada7f4e121338e9c2976390367efb76fc217 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 23:06:38 -0400 Subject: [PATCH 052/141] Focus current object/tilegroup in menu --- src/editor/tilebox.hpp | 1 + src/editor/toolbox_widget.cpp | 3 ++ src/gui/menu.cpp | 36 +++++++++++++++++-- src/gui/menu.hpp | 6 +++- src/supertux/menu/editor_objectgroup_menu.cpp | 1 + src/supertux/menu/editor_tilegroup_menu.cpp | 1 + 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/editor/tilebox.hpp b/src/editor/tilebox.hpp index 761251806d..e297f03148 100644 --- a/src/editor/tilebox.hpp +++ b/src/editor/tilebox.hpp @@ -91,6 +91,7 @@ class EditorTilebox final : public Widget float get_tiles_height() const; inline bool has_active_object_tip() const { return m_object_tip->get_visible(); } + inline size_t get_objectgroup_id() const { return m_objectgroup_id; } inline size_t get_tilegroup_id() const { return m_tilegroup_id; } private: diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index 54b5a62c5f..54e2b9202b 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -22,6 +22,7 @@ #include "editor/tool_icon.hpp" #include "gui/menu_manager.hpp" #include "gui/mousecursor.hpp" +#include "gui/menu.hpp" #include "supertux/colorscheme.hpp" #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" @@ -124,6 +125,7 @@ EditorToolboxWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) { m_editor.disable_keyboard(); MenuManager::instance().push_menu(MenuStorage::EDITOR_TILEGROUP_MENU); + MenuManager::instance().current_menu()->set_item(m_tilebox->get_tilegroup_id()); } else { @@ -137,6 +139,7 @@ EditorToolboxWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) { m_editor.disable_keyboard(); MenuManager::instance().push_menu(MenuStorage::EDITOR_OBJECTGROUP_MENU); + MenuManager::instance().current_menu()->set_item(m_tilebox->get_objectgroup_id()); } else { diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index 56113623f0..95f6c4fcc3 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -69,7 +69,8 @@ Menu::Menu() : m_items(), m_arrange_left(0), m_active_item(-1), - m_mouse_deadzone(0) + m_mouse_deadzone(0), + m_can_click_when_unfocused(false) { } @@ -343,7 +344,7 @@ Menu::previous_item() void Menu::next_item() { - if (m_active_item < m_items.size() - 1 ) + if (m_active_item < m_items.size() - 1) ++m_active_item; else m_active_item = 0; @@ -558,6 +559,34 @@ Menu::draw(DrawingContext& context) } } +void +Menu::set_item(int index) +{ + if (index < 0) + index = 0; + + m_active_item = 0; + + // Attempt to skip all skippable items + do + { + if (m_active_item > m_items.size()) + break; + + if (m_items[m_active_item]->skippable()) + { + ++m_active_item; + continue; + } + + if (index > 0) + ++m_active_item; + + --index; + } + while (index >= 0); +} + MenuItem& Menu::get_item_by_id(int id) { @@ -629,7 +658,8 @@ Menu::event(const SDL_Event& ev) mouse_pos.x < m_pos.x + get_width() / 2.0f && mouse_pos.y > m_pos.y - get_height() / 2.0f && mouse_pos.y < m_pos.y + get_height() / 2.0f) || - m_mouse_deadzone > 0) + m_mouse_deadzone > 0 || + m_can_click_when_unfocused) { process_action(MenuAction::HIT); } diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index 22a62b05b9..cc97f067b3 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -112,6 +112,7 @@ class Menu /** Remove all entries from the menu */ void clear(); + void set_item(int index); MenuItem& get_item(int index) { return *(m_items[index]); } MenuItem& get_item_by_id(int id); @@ -131,7 +132,9 @@ class Menu /** returns true when the text is more important than action */ virtual bool is_sensitive() const { return false; } - + + inline void allow_click_when_unfocused() { m_can_click_when_unfocused = true; } + protected: MenuItem& add_item(std::unique_ptr menu_item); MenuItem& add_item(std::unique_ptr menu_item, int pos_); @@ -165,6 +168,7 @@ class Menu float m_menu_height; float m_menu_help_height; int m_mouse_deadzone; + bool m_can_click_when_unfocused; public: std::vector > m_items; diff --git a/src/supertux/menu/editor_objectgroup_menu.cpp b/src/supertux/menu/editor_objectgroup_menu.cpp index a738523da8..6380f2c308 100644 --- a/src/supertux/menu/editor_objectgroup_menu.cpp +++ b/src/supertux/menu/editor_objectgroup_menu.cpp @@ -40,6 +40,7 @@ EditorObjectgroupMenu::EditorObjectgroupMenu() add_hl(); add_entry(-1,_("Cancel")); + allow_click_when_unfocused(); } EditorObjectgroupMenu::~EditorObjectgroupMenu() diff --git a/src/supertux/menu/editor_tilegroup_menu.cpp b/src/supertux/menu/editor_tilegroup_menu.cpp index a6429c2c71..63291960d2 100644 --- a/src/supertux/menu/editor_tilegroup_menu.cpp +++ b/src/supertux/menu/editor_tilegroup_menu.cpp @@ -34,6 +34,7 @@ EditorTilegroupMenu::EditorTilegroupMenu() add_hl(); add_entry(-1,_("Cancel")); + allow_click_when_unfocused(); } EditorTilegroupMenu::~EditorTilegroupMenu() From 5318eb49840df9332c0dbd87f210a71ae66e2b7c Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 19 Aug 2025 23:35:24 -0400 Subject: [PATCH 053/141] Show notification upon save via menubar --- src/editor/editor.cpp | 14 +++++++++++--- src/editor/editor.hpp | 2 +- src/gui/menu_manager.cpp | 2 ++ src/gui/notification.cpp | 4 +++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index e883445894..6267d76606 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . #include "editor/editor.hpp" +#include "gui/notification.hpp" #include #include @@ -184,7 +185,13 @@ Editor::Editor() : m_widgets.insert(m_widgets.begin() + 3, std::move(play_button)); auto save_button = std::make_unique("images/engine/editor/save.png", - Vector(128, 0), [this] { save_level(); }); + Vector(128, 0), [this] { + bool saved = save_level(); + auto notif = std::make_unique("save_level_notif"); + notif->set_text(saved ? _("Level saved!") : _("Level failed to save.")); + MenuManager::instance().set_notification(std::move(notif)); + } + ); save_button->set_help_text(_("Save level")); m_save_widget = save_button.get(); @@ -503,11 +510,11 @@ Editor::remove_autosave_file() } } -void +bool Editor::save_level(const std::string& filename, bool switch_file) { if (m_temp_level) - return; + return false; auto file = !filename.empty() ? filename : m_levelfile; @@ -521,6 +528,7 @@ Editor::save_level(const std::string& filename, bool switch_file) m_level->save(m_world ? FileSystem::join(m_world->get_basedir(), file) : file); m_time_since_last_save = 0.f; remove_autosave_file(); + return true; } std::string diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index e422416da4..6f2ec0b6c1 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -199,7 +199,7 @@ class Editor final : public Screen, * filename; subsequest saves will by default save to the * new filename. */ - void save_level(const std::string& filename = "", bool switch_file = false); + bool save_level(const std::string& filename = "", bool switch_file = false); void test_level(const std::optional>& test_pos); void update_keyboard(const Controller& controller); void keep_camera_in_bounds(); diff --git a/src/gui/menu_manager.cpp b/src/gui/menu_manager.cpp index f8fdd35fd2..7475ec7cd0 100644 --- a/src/gui/menu_manager.cpp +++ b/src/gui/menu_manager.cpp @@ -190,7 +190,9 @@ MenuManager::draw(DrawingContext& context) } if (m_notification.current) // Has current notification + { m_notification.current->draw(context); + } if ((has_dialog() || is_active()) && MouseCursor::current()) // Cursor should be drawn MouseCursor::current()->draw(context); diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index e137746321..c2ee25fea7 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -17,6 +17,7 @@ #include "gui/notification.hpp" #include "control/controller.hpp" +#include "editor/editor.hpp" #include "gui/menu_manager.hpp" #include "gui/mousecursor.hpp" #include "supertux/colorscheme.hpp" @@ -91,7 +92,8 @@ Notification::calculate_size() void Notification::draw(DrawingContext& context) { - if (m_quit || !MenuManager::instance().is_active()) // Close notification, if a quit has been requested, or the MenuManager isn't active. + // Close notification, if a quit has been requested, or the MenuManager isn't active. + if (m_quit || !(MenuManager::instance().is_active() || Editor::is_active())) { close(); return; From 62389bc115dc960148ec99059897fd4df3d3cad8 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 02:40:29 -0400 Subject: [PATCH 054/141] Save temp level as stringstream for testing Ok so, kind of a big hack. It's not really feasible at the moment to create a deep copy of the editor's level, since that involves either doing some fancy factory magic, or creating a million clone() virtual methods. So for now, we save the level as a stringstream (viable std::ostream), then pass that level for the GameSession so it will end up loading the level from the stream. It's at least better than creating a temporary file. --- src/editor/editor.cpp | 14 ++------------ src/supertux/game_manager.cpp | 14 +++++++++----- src/supertux/game_manager.hpp | 3 +++ src/supertux/game_session.cpp | 22 ++++++++++++++++------ src/supertux/game_session.hpp | 3 +++ 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 6267d76606..1d4016e736 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -187,7 +187,7 @@ Editor::Editor() : auto save_button = std::make_unique("images/engine/editor/save.png", Vector(128, 0), [this] { bool saved = save_level(); - auto notif = std::make_unique("save_level_notif"); + auto notif = std::make_unique("save_level_notif", false, true); notif->set_text(saved ? _("Level saved!") : _("Level failed to save.")); MenuManager::instance().set_notification(std::move(notif)); } @@ -555,22 +555,12 @@ Editor::test_level(const std::optional>& test_pos { Tile::draw_editor_images = false; Compositor::s_render_lighting = true; - - // Until I get testing to not clobber the editor level, display a message. - if (m_temp_level) - { - std::string message = _("You cannot test an unsaved level at the moment.\n\n" - "Please save your level before testing."); - - Dialog::show_message(message); - return; - } m_leveltested = true; if ((m_level && m_levelfile.empty()) || m_levelfile == "") { GameManager::current()->start_level(m_level.get(), test_pos); - return; + return; } std::string backup_filename = get_autosave_from_levelname(m_levelfile); diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index 635000f38e..dd9f2c1369 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -30,12 +30,16 @@ #include "util/reader.hpp" #include "util/reader_document.hpp" #include "util/reader_mapping.hpp" +#include "util/writer.hpp" #include "worldmap/tux.hpp" #include "worldmap/worldmap.hpp" #include "supertux/game_session.hpp" +#include +#include GameManager::GameManager() : - m_savegame() + m_savegame(), + m_levelstream() { } @@ -63,13 +67,13 @@ GameManager::start_level(const World& world, const std::string& level_filename, } void - - GameManager::start_level(Level* level, const std::optional>& start_pos) { - //m_current_level = std::make_unique(level); - auto screen = std::make_unique(level); + m_levelstream.clear(); + Writer writer(m_levelstream); + level->save(writer); + auto screen = std::make_unique(m_levelstream); if (start_pos) { screen->set_start_pos(start_pos->first, start_pos->second); diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 1d28ab3ac8..20132229bf 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -46,6 +46,9 @@ class GameManager final : public Currenton private: std::unique_ptr m_current_level; std::unique_ptr m_savegame; + + // Must keep stringstream in memory or else GameSession can't restart. + std::stringstream m_levelstream; private: GameManager(const GameManager&) = delete; diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index 5c54184bd4..3c509476ca 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -78,6 +78,7 @@ GameSession::GameSession(Savegame* savegame, Statistics* statistics) : m_spawn_with_invincibility(false), m_best_level_statistics(statistics), m_savegame(savegame), + m_levelstream(nullptr), m_tmp_playerstatus(0), m_play_time(0), m_levelintro_shown(false), @@ -111,6 +112,12 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat m_levelfile = levelfile_; } +GameSession::GameSession(std::istream& istream_, Savegame* savegame, Statistics* statistics) : + GameSession{savegame, statistics} +{ + m_levelstream = &istream_; +} + void GameSession::reset_level() { @@ -222,13 +229,16 @@ GameSession::restart_level(bool after_death, bool preserve_music) m_levelfile = FileSystem::basename(m_levelfile); } + // TODO: We currently storage the level as a stringstream, not ideal. // Level was passed as an argument (likely from the editor) - if (true || //temporary - m_level == nullptr && !m_levelfile.empty()) - { + // if (m_level == nullptr && !m_levelfile.empty()) + + if (!m_levelstream) m_level_storage = LevelParser::from_file(m_levelfile, false, false); - m_level = m_level_storage.get(); - } + else + m_level_storage = LevelParser::from_stream(*m_levelstream, "", false, false); + + m_level = m_level_storage.get(); /* Determine the spawnpoint to spawn/respawn Tux to. */ const GameSession::SpawnPoint* spawnpoint = nullptr; @@ -525,7 +535,7 @@ GameSession::setup() m_currentsector->get_singleton_by_type().play_music(LEVEL_MUSIC); int total_stats_to_be_collected = m_level->m_stats.m_total_coins + m_level->m_stats.m_total_badguys + m_level->m_stats.m_total_secrets; - if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0) && m_level_storage != nullptr) { + if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0) && m_savegame) { m_levelintro_shown = true; m_active = false; ScreenManager::current()->push_screen(std::make_unique(*m_level, m_best_level_statistics, m_savegame->get_player_status())); diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index 868a2d8297..3fd33cfe13 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -20,6 +20,7 @@ #include "util/currenton.hpp" #include +#include #include #include #include @@ -80,6 +81,7 @@ class GameSession final : public Screen, GameSession(Savegame* savegame = nullptr, Statistics* statistics = nullptr); GameSession(Level* level, Savegame* savegame = nullptr, Statistics* statistics = nullptr); GameSession(const std::string& levelfile, Savegame& savegame, Statistics* statistics = nullptr); + GameSession(std::istream& istream, Savegame* savegame = nullptr, Statistics* statistics = nullptr); virtual void draw(Compositor& compositor) override; virtual void update(float dt_sec, const Controller& controller) override; @@ -180,6 +182,7 @@ class GameSession final : public Screen, // the sector and spawnpoint we should spawn after this frame std::string m_newsector; std::string m_newspawnpoint; + std::istream* m_levelstream; ScreenFade::FadeType m_spawn_fade_type; Timer m_spawn_fade_timer; bool m_spawn_with_invincibility; From f3d9a1d8199d7ca6923c719d984e88678febd531 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 02:57:33 -0400 Subject: [PATCH 055/141] Clear & seek istreams on restart --- src/supertux/game_manager.cpp | 1 + src/supertux/game_session.cpp | 4 ++++ src/supertux/game_session.hpp | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index dd9f2c1369..bec9e736e3 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -70,6 +70,7 @@ void GameManager::start_level(Level* level, const std::optional>& start_pos) { + m_levelstream.str(""); m_levelstream.clear(); Writer writer(m_levelstream); level->save(writer); diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index 3c509476ca..f87f65c9c8 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -236,7 +236,11 @@ GameSession::restart_level(bool after_death, bool preserve_music) if (!m_levelstream) m_level_storage = LevelParser::from_file(m_levelfile, false, false); else + { + m_levelstream->clear(); + m_levelstream->seekg(0, std::ios::beg); m_level_storage = LevelParser::from_stream(*m_levelstream, "", false, false); + } m_level = m_level_storage.get(); diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index 3fd33cfe13..da497157d6 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -20,7 +20,7 @@ #include "util/currenton.hpp" #include -#include +#include #include #include #include From e993b2ab38faf0687f1d9e8af0cdc23a29c31134 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 03:13:52 -0400 Subject: [PATCH 056/141] Option to hide properties sidebar --- src/editor/editor.cpp | 4 +++- src/supertux/gameconfig.cpp | 1 + src/supertux/gameconfig.hpp | 1 + src/supertux/menu/editor_menu.cpp | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 1d4016e736..33394e3406 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -324,7 +324,7 @@ Editor::draw(Compositor& compositor) m_overlay_widget->draw_tilemap_outer_shading(context); m_overlay_widget->draw_tilemap_border(context); - if (m_controls.size() != 0) + if (m_controls.size() != 0 && g_config->editor_show_properties_sidebar) { context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); @@ -1451,6 +1451,8 @@ Editor::pack_addon() void Editor::addControl(const std::string& name, std::unique_ptr new_control, const std::string& description) { + if (!g_config->editor_show_properties_sidebar) + return; float height = 35.f; for (const auto& control : m_controls) { height = std::max(height, control->get_rect().get_bottom() + 5.f); diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index d0c59127b3..b6fcc7d1eb 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -109,6 +109,7 @@ Config::Config() : editor_undo_tracking(true), editor_undo_stack_size(20), editor_show_deprecated_tiles(false), + editor_show_properties_sidebar(true), multiplayer_auto_manage_players(true), multiplayer_multibind(false), #if SDL_VERSION_ATLEAST(2, 0, 9) diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index fe41179b61..5b40f1568b 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -150,6 +150,7 @@ class Config final bool editor_undo_tracking; int editor_undo_stack_size; bool editor_show_deprecated_tiles; + bool editor_show_properties_sidebar; bool multiplayer_auto_manage_players; bool multiplayer_multibind; diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index c31a04a2ec..6040f37848 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -95,6 +95,7 @@ EditorMenu::refresh() add_toggle(-1, _("Autotile Mode"), &(g_config->editor_autotile_mode)); add_toggle(-1, _("Enable Autotile Help"), &(g_config->editor_autotile_help)); add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); + add_toggle(-1, _("Show properties sidebar"), &(g_config->editor_show_properties_sidebar)); if (g_config->editor_undo_tracking) { add_intfield(_("Undo Stack Size"), &(g_config->editor_undo_stack_size), -1, true); From 8324e95af50f90c81da1702f4f800d12a47a3fd4 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 03:21:27 -0400 Subject: [PATCH 057/141] Option to hide toolbar --- src/editor/editor.cpp | 5 +++++ src/supertux/gameconfig.cpp | 1 + src/supertux/gameconfig.hpp | 1 + src/supertux/menu/editor_menu.cpp | 1 + 4 files changed, 8 insertions(+) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 33394e3406..5d0fc2b732 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -318,6 +318,11 @@ Editor::draw(Compositor& compositor) if (m_levelloaded) { for(const auto& widget : m_widgets) { + if (!g_config->editor_show_toolbar_widgets && + dynamic_cast(widget.get())) + { + continue; + } widget->draw(context); } diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index b6fcc7d1eb..be7737cd7a 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -110,6 +110,7 @@ Config::Config() : editor_undo_stack_size(20), editor_show_deprecated_tiles(false), editor_show_properties_sidebar(true), + editor_show_toolbar_widgets(true), multiplayer_auto_manage_players(true), multiplayer_multibind(false), #if SDL_VERSION_ATLEAST(2, 0, 9) diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index 5b40f1568b..088ce22a3d 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -151,6 +151,7 @@ class Config final int editor_undo_stack_size; bool editor_show_deprecated_tiles; bool editor_show_properties_sidebar; + bool editor_show_toolbar_widgets; bool multiplayer_auto_manage_players; bool multiplayer_multibind; diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index 6040f37848..0839e9abf7 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -96,6 +96,7 @@ EditorMenu::refresh() add_toggle(-1, _("Enable Autotile Help"), &(g_config->editor_autotile_help)); add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); add_toggle(-1, _("Show properties sidebar"), &(g_config->editor_show_properties_sidebar)); + add_toggle(-1, _("Show toolbar"), &(g_config->editor_show_toolbar_widgets)); if (g_config->editor_undo_tracking) { add_intfield(_("Undo Stack Size"), &(g_config->editor_undo_stack_size), -1, true); From ff617d6fd78661f4b5fd2a7359c25ae9b2afa3f4 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 03:42:06 -0400 Subject: [PATCH 058/141] Scroll on tiles/objects buttons to switch categories --- src/editor/tilebox.cpp | 37 +++++++++++++++++++++++++++++++++++ src/editor/tilebox.hpp | 5 ++++- src/editor/toolbox_widget.cpp | 31 +++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/editor/tilebox.cpp b/src/editor/tilebox.cpp index 62cc4481d4..aa2b101fc1 100644 --- a/src/editor/tilebox.cpp +++ b/src/editor/tilebox.cpp @@ -401,6 +401,43 @@ EditorTilebox::select_last_objectgroup() select_objectgroup(m_objectgroup_id); } +void +EditorTilebox::change_tilegroup(int dir) +{ + if (m_input_type == InputType::OBJECT) + { + select_last_tilegroup(); + return; + } + + m_tilegroup_id += dir; + size_t tilegroups_size = m_editor.get_tileset()->get_tilegroups().size(); + if (m_tilegroup_id < 0) + m_tilegroup_id = tilegroups_size - 1; + else if (m_tilegroup_id > tilegroups_size - 1) + m_tilegroup_id = 0; + + select_last_tilegroup(); +} + +void +EditorTilebox::change_objectgroup(int dir) +{ + if (m_input_type == InputType::TILE) + { + select_last_objectgroup(); + return; + } + + m_objectgroup_id += dir; + size_t objectgroups_size = m_object_info->m_groups.size(); + if (m_objectgroup_id < 0) + m_objectgroup_id = objectgroups_size - 1; + else if (m_objectgroup_id > objectgroups_size - 1) + m_objectgroup_id = 0; + + select_last_objectgroup(); +} bool EditorTilebox::select_layers_objectgroup() diff --git a/src/editor/tilebox.hpp b/src/editor/tilebox.hpp index e297f03148..e52c1b4dc7 100644 --- a/src/editor/tilebox.hpp +++ b/src/editor/tilebox.hpp @@ -93,6 +93,9 @@ class EditorTilebox final : public Widget inline bool has_active_object_tip() const { return m_object_tip->get_visible(); } inline size_t get_objectgroup_id() const { return m_objectgroup_id; } inline size_t get_tilegroup_id() const { return m_tilegroup_id; } + + void change_tilegroup(int dir); + void change_objectgroup(int dir); private: Vector get_tile_coords(int pos, bool relative = true) const; @@ -124,7 +127,7 @@ class EditorTilebox final : public Widget std::unique_ptr m_active_tilegroup; ObjectGroup* m_active_objectgroup; std::unique_ptr m_object_info; - size_t m_tilegroup_id, m_objectgroup_id; + int m_tilegroup_id, m_objectgroup_id; std::function m_on_select_callback; diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index 54e2b9202b..876976395e 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -267,6 +267,37 @@ EditorToolboxWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) bool EditorToolboxWidget::on_mouse_wheel(const SDL_MouseWheelEvent& wheel) { + switch (m_hovered_item) + { + case HoveredItem::TILEGROUP: + if (m_editor.get_tileset()->get_tilegroups().size() > 1) + { + m_tilebox->change_tilegroup(wheel.y > 0 ? -1 : 1); + } + else + { + select_tilegroup(0); + } + break; + + case HoveredItem::OBJECTS: + if ((m_editor.get_level()->is_worldmap() && m_tilebox->get_object_info().get_num_worldmap_groups() > 1) || + (!m_editor.get_level()->is_worldmap() && m_tilebox->get_object_info().get_num_level_groups() > 1)) + { + m_tilebox->change_objectgroup(wheel.y > 0 ? -1 : 1); + } + else + { + if (m_editor.get_level()->is_worldmap()) + select_objectgroup(m_tilebox->get_object_info().get_first_worldmap_group_index()); + else + select_objectgroup(0); + } + break; + + default: + break; + } return m_tilebox->on_mouse_wheel(wheel); } From aefc4f7ec2c103e8c70d773c2095d95ab45d64ce Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 03:59:03 -0400 Subject: [PATCH 059/141] Ctrl+shift+t to spawn player at cursor --- src/editor/editor.cpp | 15 ++++++++++++++- src/editor/editor.hpp | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 5d0fc2b732..220fe32151 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1114,7 +1114,13 @@ Editor::event(const SDL_Event& ev) switch (ev.key.keysym.sym) { case SDLK_t: - test_level(std::nullopt); + if (m_shift_pressed) + { + std::pair spawn{get_sector()->get_name(), get_abs_mouse_pos()}; + test_level(spawn); + } + else + test_level(std::nullopt); break; case SDLK_s: save_level(); @@ -1177,6 +1183,13 @@ Editor::event(const SDL_Event& ev) } } +Vector +Editor::get_abs_mouse_pos() +{ + Camera& camera = m_sector->get_camera(); + return camera.get_position() + m_mouse_pos; +} + void Editor::toggle_tile_object_mode() { diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 6f2ec0b6c1..d8c947e25f 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -116,6 +116,8 @@ class Editor final : public Screen, m_levelfile = levelfile; m_reload_request = true; } + + Vector get_abs_mouse_pos(); std::string get_level_directory() const; From 16565abc87fab2790ce9496c3900350d54b0eea2 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 04:14:56 -0400 Subject: [PATCH 060/141] Fix building on hostile operating systems --- src/supertux/game_manager.cpp | 1 - src/supertux/game_manager.hpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index bec9e736e3..bf62550717 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -35,7 +35,6 @@ #include "worldmap/worldmap.hpp" #include "supertux/game_session.hpp" #include -#include GameManager::GameManager() : m_savegame(), diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 20132229bf..0a0a500905 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "math/vector.hpp" From 6c06dfa48d34454e931118c9a357ad86e5913039 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 16:18:51 -0400 Subject: [PATCH 061/141] Edit scripts with external editors --- src/editor/editor.cpp | 7 +- src/editor/editor.hpp | 3 + src/editor/object_option.cpp | 4 +- src/editor/object_settings.cpp | 3 +- src/editor/overlay_widget.cpp | 4 +- src/gui/item_script.cpp | 7 +- src/gui/item_script.hpp | 3 +- src/gui/menu.cpp | 4 +- src/gui/menu.hpp | 2 +- src/gui/menu_script.cpp | 20 ++++- src/gui/menu_script.hpp | 5 +- src/supertux/gameconfig.cpp | 3 +- src/supertux/gameconfig.hpp | 1 + src/supertux/menu/editor_menu.cpp | 1 + src/supertux/menu/editor_sector_menu.cpp | 2 +- src/util/file_system.cpp | 35 +++++++++ src/util/file_system.hpp | 5 ++ src/util/file_watcher.cpp | 59 +++++++++++++++ src/util/file_watcher.hpp | 53 ++++++++++++++ src/util/script_manager.cpp | 93 ++++++++++++++++++++++++ src/util/script_manager.hpp | 57 +++++++++++++++ 21 files changed, 352 insertions(+), 19 deletions(-) create mode 100644 src/util/file_watcher.cpp create mode 100644 src/util/file_watcher.hpp create mode 100644 src/util/script_manager.cpp create mode 100644 src/util/script_manager.hpp diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 220fe32151..109542cea3 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -141,7 +141,8 @@ Editor::Editor() : m_scroll_speed(32.0f), m_new_scale(0.f), m_mouse_pos(0.f, 0.f), - m_layers_widget_needs_refresh(false) + m_layers_widget_needs_refresh(false), + m_script_manager() { auto toolbox_widget = std::make_unique(*this); auto layers_widget = std::make_unique(*this); @@ -154,7 +155,7 @@ Editor::Editor() : m_widgets.push_back(std::move(toolbox_widget)); m_widgets.push_back(std::move(layers_widget)); m_widgets.push_back(std::move(overlay_widget)); - + auto grid_size_widget = std::make_unique("images/engine/editor/grid_button.png", Vector(64, 0), [this] { @@ -412,6 +413,8 @@ Editor::update(float dt_sec, const Controller& controller) m_time_since_last_save = 0.f; } + m_script_manager.poll(); + // Pass all requests. if (m_reload_request) { reload_level(); diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index d8c947e25f..6c665a49a1 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -33,6 +33,7 @@ #include "util/currenton.hpp" #include "util/file_system.hpp" #include "util/log.hpp" +#include "util/script_manager.hpp" #include "util/string_util.hpp" #include "video/surface_ptr.hpp" @@ -230,6 +231,8 @@ class Editor final : public Screen, bool m_ctrl_pressed; bool m_shift_pressed; + + ScriptManager m_script_manager; private: Sector* m_sector; diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 8c713ab01a..92fe2534c2 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -337,7 +337,7 @@ StringMultilineObjectOption::to_string() const void StringMultilineObjectOption::add_to_menu(Menu& menu) const { - menu.add_script(get_text(), m_value_pointer); + menu.add_script(m_key, get_text(), m_value_pointer); } StringSelectObjectOption::StringSelectObjectOption(const std::string& text, int* pointer, @@ -486,7 +486,7 @@ ScriptObjectOption::to_string() const void ScriptObjectOption::add_to_menu(Menu& menu) const { - menu.add_script(get_text(), m_value_pointer); + menu.add_script(m_key, get_text(), m_value_pointer); } FileObjectOption::FileObjectOption(const std::string& text, std::string* pointer, diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index c314e74100..f62ab4b3f8 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -19,6 +19,7 @@ #include #include +#include "editor/editor.hpp" #include "editor/object_option.hpp" #include "util/gettext.hpp" #include "util/log.hpp" @@ -292,7 +293,7 @@ ObjectSettings::add_level(const std::string& text, std::string* value_ptr, const const std::string& basedir, unsigned int flags) { - add_file(text, value_ptr, key, {}, {".stl"}, basedir, true, flags); + add_file(text, value_ptr, key, {}, {".stl"}, basedir, true, flags); } void diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index b5ac094ffd..16359276bc 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -749,8 +749,8 @@ EditorOverlayWidget::update_properties_panel(GameObject* object) { auto button = std::make_unique(_("Edit...")); const auto value_ptr = script_option->get_value(); - button.get()->m_on_change = std::function([value_ptr]() { - MenuManager::instance().push_menu(std::make_unique(value_ptr)); + button.get()->m_on_change = std::function([value_ptr, script_option]() { + MenuManager::instance().push_menu(std::make_unique(script_option->get_key(), value_ptr)); }); button.get()->set_rect(Rectf(0, 32, 20, 32)); m_editor.addControl(text, std::move(button), description); diff --git a/src/gui/item_script.cpp b/src/gui/item_script.cpp index 9a3a8db02f..8deedaaacd 100644 --- a/src/gui/item_script.cpp +++ b/src/gui/item_script.cpp @@ -20,15 +20,16 @@ #include "gui/menu_manager.hpp" #include "gui/menu_script.hpp" -ItemScript::ItemScript(const std::string& text, std::string* script_, int id) : +ItemScript::ItemScript(const std::string& key, const std::string& text, std::string* script_, int id) : MenuItem(text, id), - script(script_) + script(script_), + m_key(std::move(key)) { } void ItemScript::process_action(const MenuAction& action) { if (action == MenuAction::HIT) { - MenuManager::instance().push_menu(std::make_unique(script)); + MenuManager::instance().push_menu(std::make_unique(m_key, script)); } } diff --git a/src/gui/item_script.hpp b/src/gui/item_script.hpp index fd3d792a09..4b641d14aa 100644 --- a/src/gui/item_script.hpp +++ b/src/gui/item_script.hpp @@ -21,13 +21,14 @@ class ItemScript final : public MenuItem { public: - ItemScript(const std::string& text_, std::string* script_, int id = -1); + ItemScript(const std::string& key, const std::string& text_, std::string* script_, int id = -1); /** Processes the menu action. */ virtual void process_action(const MenuAction& action) override; private: std::string* script; + std::string m_key; private: ItemScript(const ItemScript&) = delete; diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index 95f6c4fcc3..16c6803967 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -167,9 +167,9 @@ Menu::add_textfield(const std::string& text, std::string* input, int id) } ItemScript& -Menu::add_script(const std::string& text, std::string* script, int id) +Menu::add_script(const std::string& key, const std::string& text, std::string* script, int id) { - return add_item(text, script, id); + return add_item(key, text, script, id); } ItemIntField& diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index cc97f067b3..5b7cb9f73d 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -90,7 +90,7 @@ class Menu ItemStringSelect& add_string_select(int id, const std::string& text, int* selected, const std::vector& strings); ItemStringSelect& add_string_select(int id, const std::string& text, int default_item, const std::vector& strings); ItemTextField& add_textfield(const std::string& text, std::string* input, int id = -1); - ItemScript& add_script(const std::string& text, std::string* script, int id = -1); + ItemScript& add_script(const std::string& key, const std::string& text, std::string* script, int id = -1); ItemIntField& add_intfield(const std::string& text, int* input, int id = -1, bool positive = false); ItemFloatField& add_floatfield(const std::string& text, float* input, int id = -1, bool positive = false); ItemAction& add_file(const std::string& text, std::string* input, const std::vector& extensions, diff --git a/src/gui/menu_script.cpp b/src/gui/menu_script.cpp index d044a035c1..5478a0d19b 100644 --- a/src/gui/menu_script.cpp +++ b/src/gui/menu_script.cpp @@ -16,12 +16,15 @@ #include "gui/menu_script.hpp" +#include "editor/editor.hpp" #include "gui/item_script_line.hpp" #include "util/gettext.hpp" -ScriptMenu::ScriptMenu(std::string* script_) : +ScriptMenu::ScriptMenu(const std::string& key, std::string* script_) : base_script(script_), - script_strings() + script_strings(), + m_start_time(time(0)), + m_key(key) { script_strings.clear(); @@ -42,12 +45,25 @@ ScriptMenu::ScriptMenu(std::string* script_) : //add_script_line(base_script); + if (Editor::current()) + Editor::current()->m_script_manager.register_script(m_key, base_script); + add_hl(); + add_entry(_("Open in editor"), [this]{ + FileSystem::open_editor(ScriptManager::full_filename_from_key(m_key)); + }); add_back(_("OK")); } ScriptMenu::~ScriptMenu() { + time_t mtime = Editor::current()->m_script_manager.get_mtime( + ScriptManager::full_filename_from_key(m_key)); + + // Don't save if the external file was edited. + if (mtime > m_start_time) + return; + *base_script = *(script_strings[0]); for (auto i = script_strings.begin()+1; i != script_strings.end(); ++i) { *base_script += "\n" + **i; diff --git a/src/gui/menu_script.hpp b/src/gui/menu_script.hpp index 9c55a0f35c..41bf40602e 100644 --- a/src/gui/menu_script.hpp +++ b/src/gui/menu_script.hpp @@ -16,6 +16,7 @@ #pragma once +#include #include "gui/menu.hpp" class ItemScriptLine; @@ -23,7 +24,7 @@ class ItemScriptLine; class ScriptMenu final : public Menu { public: - ScriptMenu(std::string* script_); + ScriptMenu(const std::string& key, std::string* script_); ~ScriptMenu() override; void menu_action(MenuItem& item) override; @@ -35,8 +36,10 @@ class ScriptMenu final : public Menu bool is_sensitive() const override; private: + time_t m_start_time; std::string* base_script; std::vector > script_strings; + std::string m_key; void push_string(const std::string& new_line); diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index be7737cd7a..17b43a0a87 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -121,7 +121,8 @@ Config::Config() : // and those with an older SDL; they won't have to check the setting each time. multiplayer_buzz_controllers(false), #endif - repository_url() + repository_url(), + preferred_text_editor() { } diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index 088ce22a3d..8a882badcd 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -152,6 +152,7 @@ class Config final bool editor_show_deprecated_tiles; bool editor_show_properties_sidebar; bool editor_show_toolbar_widgets; + std::string preferred_text_editor; bool multiplayer_auto_manage_players; bool multiplayer_multibind; diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index 0839e9abf7..78897b623f 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -97,6 +97,7 @@ EditorMenu::refresh() add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); add_toggle(-1, _("Show properties sidebar"), &(g_config->editor_show_properties_sidebar)); add_toggle(-1, _("Show toolbar"), &(g_config->editor_show_toolbar_widgets)); + add_textfield(_("Default editor"), &(g_config->preferred_text_editor)); if (g_config->editor_undo_tracking) { add_intfield(_("Undo Stack Size"), &(g_config->editor_undo_stack_size), -1, true); diff --git a/src/supertux/menu/editor_sector_menu.cpp b/src/supertux/menu/editor_sector_menu.cpp index ab4cc70cd3..19fb1992c2 100644 --- a/src/supertux/menu/editor_sector_menu.cpp +++ b/src/supertux/menu/editor_sector_menu.cpp @@ -34,7 +34,7 @@ EditorSectorMenu::EditorSectorMenu() : add_label(fmt::format(fmt::runtime(_("Sector {}")), sector->get_name())); add_hl(); add_textfield(_("Name"), §or->m_name); - add_script(_("Initialization script"), §or->m_init_script); + add_script(sector->m_name, _("Initialization script"), §or->m_init_script); add_floatfield(_("Gravity"), §or->m_gravity); add_hl(); diff --git a/src/util/file_system.cpp b/src/util/file_system.cpp index 030c11836f..835a821691 100644 --- a/src/util/file_system.cpp +++ b/src/util/file_system.cpp @@ -15,12 +15,15 @@ // along with this program. If not, see . #include "util/file_system.hpp" +#include "supertux/globals.hpp" #include +#include #include #include #include #include +#include #include #if defined(_WIN32) #include @@ -43,6 +46,7 @@ #include "gui/dialog.hpp" #include "util/log.hpp" #include "util/string_util.hpp" +#include "supertux/gameconfig.hpp" namespace fs = std::filesystem; @@ -235,6 +239,37 @@ void open_path(const std::string& path) #endif } +void +open_editor(const std::string& filename) +{ +#if defined(_WIN32) || defined(_WIN64) + ShellExecute(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL); +#else + #if defined(__APPLE__) + std::string cmd = "open \"" + filename + "\""; + #else + const char* default_editor = getenv("EDITOR"); + std::string cmd; + if (!g_config->preferred_text_editor.empty()) + cmd = g_config->preferred_text_editor + " \"" + filename + "\" &"; + else if (default_editor) + { + cmd = std::string(default_editor) + " \"" + filename + "\" &"; + } + #endif + + int ret = system(cmd.c_str()); + if (ret < 0) + { + log_fatal << "failed to spawn editor: " << cmd << std::endl; + } + else if (ret > 0) + { + log_fatal << "error " << ret << " while executing: " << cmd << std::endl; + } +#endif +} + std::string escape_url(const std::string& url) { #ifndef __EMSCRIPTEN__ diff --git a/src/util/file_system.hpp b/src/util/file_system.hpp index ba6bf77200..bbc49edae8 100644 --- a/src/util/file_system.hpp +++ b/src/util/file_system.hpp @@ -66,6 +66,11 @@ std::string escape_url(const std::string& url); * @param path path to open */ void open_path(const std::string& path); + +/** Opens a file in the users preferred text editor. + * @param filename File to edit + */ +void open_editor(const std::string& filename); /** Opens an URL in the user's preferred browser. * @param url URL to open diff --git a/src/util/file_watcher.cpp b/src/util/file_watcher.cpp new file mode 100644 index 0000000000..814f8c887d --- /dev/null +++ b/src/util/file_watcher.cpp @@ -0,0 +1,59 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include "util/file_watcher.hpp" + +FileWatcher::FileWatcher() +{ +} + +time_t +FileWatcher::get_mtime(const std::string &filename) +{ + struct stat file_stat; + if (stat(filename.c_str(), &file_stat) != 0) + { + // TODO + return 0; + } + + return file_stat.st_mtime; +} + +void +FileWatcher::start_monitoring(std::string filename, FileWatcher::callback_t fun) +{ + m_files.emplace(std::move(filename), FileWatcher::FileInfo{filename, get_mtime(filename), fun}); +} + +void +FileWatcher::poll() +{ + struct stat file_stat; + for (auto &file : m_files) + { + FileInfo &finfo = file.second; + time_t mtime = get_mtime(file.first); + + // Same time, don't bother + if (finfo.m_last_mtime == mtime) + continue; + + finfo.callback(finfo); + finfo.m_last_mtime = mtime; + } +} diff --git a/src/util/file_watcher.hpp b/src/util/file_watcher.hpp new file mode 100644 index 0000000000..4478b2cd24 --- /dev/null +++ b/src/util/file_watcher.hpp @@ -0,0 +1,53 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include +#include +#include + +class FileWatcher +{ +public: + struct FileInfo; + using callback_t = std::function; + + struct FileInfo { + std::string filename; + time_t m_last_mtime; + callback_t callback; + + bool operator==(struct FileInfo& other) const { + return other.filename == filename; + } + bool operator==(const std::string& other) const { + return other == filename; + } + }; +public: + FileWatcher(); + ~FileWatcher() = default; + + void start_monitoring(std::string filename, callback_t fun); + + void poll(); + time_t get_mtime(const std::string &filename); +private: + + std::unordered_map m_files; +}; diff --git a/src/util/script_manager.cpp b/src/util/script_manager.cpp new file mode 100644 index 0000000000..ae86368cd4 --- /dev/null +++ b/src/util/script_manager.cpp @@ -0,0 +1,93 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "script_manager.hpp" +#include "util/file_system.hpp" +#include "util/file_watcher.hpp" +#include +#include +#include +#include + +ScriptManager::ScriptManager() : + m_watcher() +{ + +} + +bool +ScriptManager::is_script_registered(const std::string& key) +{ + auto res = std::find(m_scripts.begin(), m_scripts.end(), key); + return res != m_scripts.end(); +} + +std::string +ScriptManager::full_filename_from_key(const std::string& key) +{ + return FileSystem::join(PHYSFS_getWriteDir(), "tmp/" + filename_from_key(key)); +} + +std::string +ScriptManager::filename_from_key(const std::string& key) +{ + return key + "_" + std::to_string(std::hash()(key)).substr(0, 6) + ".nut"; +} + +time_t +ScriptManager::get_mtime(const std::string& key) +{ + return m_watcher.get_mtime(key); +} + +void +ScriptManager::register_script(std::string key, std::string* script) +{ + std::string full_filename = full_filename_from_key(key); + + // TODO: Check if it's different. Causes some editors to nag. + // Write current script contents + std::ofstream file; + file.open(full_filename); + file << *script; + file.close(); + + if (is_script_registered(key)) + return; + m_scripts.push_back({full_filename, script}); + + m_watcher.start_monitoring(full_filename, [this](FileWatcher::FileInfo& file) { + auto res = std::find(m_scripts.begin(), m_scripts.end(), file.filename); + if (res == m_scripts.end()) + return; + + *res->script = ""; + std::fstream readme; + readme.open(file.filename); + std::string line; + while (std::getline(readme, line)) + { + *res->script += line + "\n"; + } + readme.close(); + }); +} + +void +ScriptManager::poll() +{ + m_watcher.poll(); +} diff --git a/src/util/script_manager.hpp b/src/util/script_manager.hpp new file mode 100644 index 0000000000..dbdf1487c1 --- /dev/null +++ b/src/util/script_manager.hpp @@ -0,0 +1,57 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "util/file_watcher.hpp" +#include +#include +#include + +class ScriptManager +{ +public: + using callback_t = std::function; + + struct ScriptInfo { + std::string key; + std::string* script; + + bool operator==(struct ScriptInfo& other) const { + return other.key == key; + } + bool operator==(const std::string& other) const { + return other == key; + } + }; +public: + ScriptManager(); + ~ScriptManager() = default; + + static std::string filename_from_key(const std::string& key); + static std::string full_filename_from_key(const std::string& key); + + time_t get_mtime(const std::string& key); + + bool is_script_registered(const std::string& key); + void register_script(std::string key, std::string* script); + + void poll(); +private: + + std::vector m_scripts; + FileWatcher m_watcher; +}; From a326b94909746ffe07cdc1fa17915e351b8ce30a Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 16:32:10 -0400 Subject: [PATCH 062/141] Store new settings --- src/supertux/gameconfig.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index 17b43a0a87..bc6ec4768f 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -248,6 +248,8 @@ Config::load() editor_undo_stack_size = 1; } editor_mapping->get("show_deprecated_tiles", editor_show_deprecated_tiles); + editor_mapping->get("show_properties_sidebar", editor_show_properties_sidebar); + editor_mapping->get("show_toolbar_widgets", editor_show_toolbar_widgets); } if (is_christmas()) { @@ -261,6 +263,7 @@ Config::load() config_mapping.get("multiplayer_auto_manage_players", multiplayer_auto_manage_players); config_mapping.get("multiplayer_multibind", multiplayer_multibind); config_mapping.get("multiplayer_buzz_controllers", multiplayer_buzz_controllers); + config_mapping.get("preferred_text_editor", preferred_text_editor); std::optional config_video_mapping; if (config_mapping.get("video", config_video_mapping)) @@ -410,6 +413,7 @@ Config::save() writer.write("multiplayer_auto_manage_players", multiplayer_auto_manage_players); writer.write("multiplayer_multibind", multiplayer_multibind); writer.write("multiplayer_buzz_controllers", multiplayer_buzz_controllers); + writer.write("preferred_text_editor", preferred_text_editor); writer.start_list("interface_colors"); writer.write("menubackcolor", menubackcolor.toVector()); @@ -504,6 +508,8 @@ Config::save() writer.write("undo_tracking", editor_undo_tracking); writer.write("undo_stack_size", editor_undo_stack_size); writer.write("show_deprecated_tiles", editor_show_deprecated_tiles); + writer.write("show_properties_sidebar", editor_show_properties_sidebar); + writer.write("show_toolbar_widgets", editor_show_toolbar_widgets); } writer.end_list("editor"); From 910ec9b9c33f0ba1095fabc5ed8dc763047d2027 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Wed, 20 Aug 2025 22:42:55 -0400 Subject: [PATCH 063/141] Edit level button with dev mode enabled Will probably improve this later --- src/editor/editor.cpp | 8 ++++++++ src/editor/editor.hpp | 3 +++ src/supertux/menu/game_menu.cpp | 25 +++++++++++++++++++++++++ src/supertux/menu/game_menu.hpp | 1 + 4 files changed, 37 insertions(+) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 109542cea3..fac65308e0 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -135,6 +135,7 @@ Editor::Editor() : m_overlay_widget(), m_toolbox_widget(), m_layers_widget(), + m_testing_disabled(false), m_enabled(false), m_bgr_surface(Surface::from_file("images/engine/menu/bg_editor.png")), m_time_since_last_save(0.f), @@ -561,6 +562,13 @@ Editor::get_level_directory() const void Editor::test_level(const std::optional>& test_pos) { + if (m_testing_disabled) + { + Dialog::show_message(_("You cannot test a level when playing from the worldmap.\n\n" + "Exit the level editor instead.")); + return; + } + Tile::draw_editor_images = false; Compositor::s_render_lighting = true; diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 6c665a49a1..1c91f6efa2 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -149,6 +149,8 @@ class Editor final : public Screen, void esc_press(); void delete_markers(); void sort_layers(); + + inline void disable_testing() { m_testing_disabled = true; } void select_tilegroup(int id); void select_last_tilegroup(); @@ -225,6 +227,7 @@ class Editor final : public Screen, bool m_save_request_switch; bool m_test_request; bool m_particle_editor_request; + bool m_testing_disabled; std::optional> m_test_pos; std::string* m_particle_editor_filename; diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index 2f1668c2e6..b921b6aad2 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -16,14 +16,18 @@ #include "supertux/menu/game_menu.hpp" +#include "audio/sound_manager.hpp" +#include "editor/editor.hpp" #include "gui/dialog.hpp" #include "gui/menu_item.hpp" #include "gui/menu_manager.hpp" +#include "supertux/fadetoblack.hpp" #include "supertux/game_session.hpp" #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" #include "supertux/level.hpp" #include "supertux/menu/menu_storage.hpp" +#include "supertux/screen_manager.hpp" #include "supertux/sector.hpp" #include "object/player.hpp" #include "util/gettext.hpp" @@ -54,6 +58,11 @@ GameMenu::GameMenu() : if (Sector::current()->get_players()[0]->get_status().can_reach_checkpoint()) { add_entry(MNID_RESETLEVELCHECKPOINT, _("Restart from Checkpoint")); } + + if (g_config->developer_mode) + { + add_entry(MNID_EDITLEVEL, _("Edit Level")); + } add_submenu(_("Options"), MenuStorage::INGAME_OPTIONS_MENU); add_hl(); @@ -92,6 +101,22 @@ GameMenu::menu_action(MenuItem& item) reset_checkpoint_callback(); } break; + + case MNID_EDITLEVEL: + { + if (Editor::is_active()) + break; + MenuManager::instance().clear_menu_stack(); + Editor* editor = new Editor(); + editor->set_level(GameSession::current()->get_level_file()); + editor->update(0, Controller()); + editor->disable_testing(); + GameSession::current()->restart_level(); + std::unique_ptr screen(std::move(editor)); + ScreenManager::current()->push_screen(std::move(screen)); + SoundManager::current()->stop_music(0.5); + } + break; case MNID_ABORTLEVEL: if (g_config->confirmation_dialog) diff --git a/src/supertux/menu/game_menu.hpp b/src/supertux/menu/game_menu.hpp index 00c125ed76..aa5d66a5ff 100644 --- a/src/supertux/menu/game_menu.hpp +++ b/src/supertux/menu/game_menu.hpp @@ -40,6 +40,7 @@ class GameMenu final : public Menu MNID_CONTINUE, MNID_RESETLEVEL, MNID_RESETLEVELCHECKPOINT, + MNID_EDITLEVEL, MNID_ABORTLEVEL }; From dbab053557b49726bf8007e0635fadf4dd82deed Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 21 Aug 2025 13:41:28 -0400 Subject: [PATCH 064/141] Properly start level from menu This is a weird hack to essentially queue up the creation of the editor when the user selects the Edit level option. Otherwise the Currenton's get clobbered. This ensures they are deallocated by doing a callback. --- src/editor/editor.cpp | 5 ++++- src/editor/editor.hpp | 4 ++++ src/supertux/game_manager.cpp | 5 +---- src/supertux/game_manager.hpp | 4 +++- src/supertux/menu/game_menu.cpp | 39 ++++++++++++++++++++++++++------- src/supertux/screen_manager.cpp | 21 +++++++++++++++--- src/supertux/screen_manager.hpp | 11 +++++++--- src/worldmap/worldmap.cpp | 29 +++++++++++++++++++----- src/worldmap/worldmap.hpp | 11 ++++++++-- 9 files changed, 101 insertions(+), 28 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index fac65308e0..ad00f7e70f 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -143,7 +143,8 @@ Editor::Editor() : m_new_scale(0.f), m_mouse_pos(0.f, 0.f), m_layers_widget_needs_refresh(false), - m_script_manager() + m_script_manager(), + m_on_exit_cb(nullptr) { auto toolbox_widget = std::make_unique(*this); auto layers_widget = std::make_unique(*this); @@ -292,6 +293,8 @@ Editor::Editor() : Editor::~Editor() { + if (m_on_exit_cb) + m_on_exit_cb(); } void diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 1c91f6efa2..67a7c1fedc 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -51,6 +51,7 @@ class Editor final : public Screen, public Currenton { public: + using exit_cb_t = std::function; static bool is_active(); private: @@ -191,6 +192,7 @@ class Editor final : public Screen, void redo(); void pack_addon(); + inline void on_exit(exit_cb_t exit_cb) { m_on_exit_cb = exit_cb; } private: void set_sector(Sector* sector); @@ -236,6 +238,8 @@ class Editor final : public Screen, bool m_shift_pressed; ScriptManager m_script_manager; + + exit_cb_t m_on_exit_cb; private: Sector* m_sector; diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index bf62550717..f30233a904 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -106,11 +106,8 @@ GameManager::start_worldmap(const World& world, const std::string& worldmap_file filename = world.get_worldmap_filename(); } - auto worldmap = std::make_unique(filename, *m_savegame, sector, spawnpoint); + auto worldmap = std::make_unique(filename, *m_savegame, sector, spawnpoint, world.get_basename()); ScreenManager::current()->push_screen(std::move(worldmap)); - - if (!Editor::current()) - m_savegame->get_profile().set_last_world(world.get_basename()); } catch (const std::exception& e) { diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 0a0a500905..8f30ce37af 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -44,9 +44,11 @@ class GameManager final : public Currenton const std::optional>& start_pos = std::nullopt); void start_level(Level* level, const std::optional>& start_pos = std::nullopt); +public: + std::unique_ptr m_savegame; + private: std::unique_ptr m_current_level; - std::unique_ptr m_savegame; // Must keep stringstream in memory or else GameSession can't restart. std::stringstream m_levelstream; diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index b921b6aad2..7f4699606e 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -22,6 +22,7 @@ #include "gui/menu_item.hpp" #include "gui/menu_manager.hpp" #include "supertux/fadetoblack.hpp" +#include "supertux/game_manager.hpp" #include "supertux/game_session.hpp" #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" @@ -31,6 +32,7 @@ #include "supertux/sector.hpp" #include "object/player.hpp" #include "util/gettext.hpp" +#include "worldmap/worldmap.hpp" GameMenu::GameMenu() : reset_callback ( [] { @@ -107,14 +109,35 @@ GameMenu::menu_action(MenuItem& item) if (Editor::is_active()) break; MenuManager::instance().clear_menu_stack(); - Editor* editor = new Editor(); - editor->set_level(GameSession::current()->get_level_file()); - editor->update(0, Controller()); - editor->disable_testing(); - GameSession::current()->restart_level(); - std::unique_ptr screen(std::move(editor)); - ScreenManager::current()->push_screen(std::move(screen)); - SoundManager::current()->stop_music(0.5); + //Editor* editor = new Editor(); + //editor->disable_testing(); + std::string level_file = GameSession::current()->get_level_file(); + std::string return_to = worldmap::WorldMap::current()->get_basename(); + ScreenManager::current()->pop_screen(); + ScreenManager::current()->pop_screen(); + ScreenManager::current()->push_screen([level_file, return_to]() { + Editor* editor = new Editor(); + editor->set_level(level_file); + editor->update(0, Controller()); + editor->on_exit([return_to]() { + ScreenManager::current()->push_screen([return_to]() { + std::unique_ptr world = World::from_directory(return_to); + GameManager::current()->m_savegame = Savegame::from_current_profile(world->get_basename()); + auto filename = GameManager::current()->m_savegame->get_player_status().last_worldmap; + if (filename.empty()) + filename = world->get_worldmap_filename(); + auto worldmap = new worldmap::WorldMap( + filename, *GameManager::current()->m_savegame, + "", "", world->get_basename()); + worldmap->start_level(); + return worldmap; + }); + }); + + return editor; + }); + //std::unique_ptr screen(std::move(editor)); + //ScreenManager::current()->push_screen(std::move(screen)); } break; diff --git a/src/supertux/screen_manager.cpp b/src/supertux/screen_manager.cpp index 425babe382..be4fa93cb7 100644 --- a/src/supertux/screen_manager.cpp +++ b/src/supertux/screen_manager.cpp @@ -159,7 +159,16 @@ ScreenManager::push_screen(std::unique_ptr screen, std::unique_ptr callback, std::unique_ptr screen_fade) +{ + log_debug << "ScreenManager::push_screen(): (lambda) " << &callback << std::endl; + assert(callback); + set_screen_fade(std::move(screen_fade)); + m_actions.emplace_back(Action::PUSH_ACTION, nullptr, std::move(callback)); } void @@ -516,8 +525,14 @@ ScreenManager::handle_screen_switch() break; case Action::PUSH_ACTION: - assert(action.screen); - m_screen_stack.push_back(std::move(action.screen)); + if (!action.screen && action.callback) + { + m_screen_stack.push_back(std::unique_ptr(action.callback())); + } + else + { + m_screen_stack.push_back(std::move(action.screen)); + } break; case Action::QUIT_ACTION: diff --git a/src/supertux/screen_manager.hpp b/src/supertux/screen_manager.hpp index a0a221c698..331e2c3d00 100644 --- a/src/supertux/screen_manager.hpp +++ b/src/supertux/screen_manager.hpp @@ -43,6 +43,7 @@ class VideoSystem; class ScreenManager final : public Currenton { public: + using callback_t = std::function; ScreenManager(VideoSystem& video_system, InputManager& input_manager); ~ScreenManager() override; @@ -56,6 +57,7 @@ class ScreenManager final : public Currenton // push new screen on screen_stack void push_screen(std::unique_ptr screen, std::unique_ptr fade = {}); + void push_screen(callback_t callback, std::unique_ptr fade = {}); void pop_screen(std::unique_ptr fade = {}); void set_screen_fade(std::unique_ptr fade); @@ -91,11 +93,14 @@ class ScreenManager final : public Currenton enum Type { PUSH_ACTION, POP_ACTION, QUIT_ACTION }; Type type; std::unique_ptr screen; - + callback_t callback; + Action(Type type_, - std::unique_ptr screen_ = {}) : + std::unique_ptr screen_ = {}, + callback_t cb = nullptr) : type(type_), - screen(std::move(screen_)) + screen(std::move(screen_)), + callback(std::move(cb)) {} }; diff --git a/src/worldmap/worldmap.cpp b/src/worldmap/worldmap.cpp index 271064d927..3a73daa0f7 100644 --- a/src/worldmap/worldmap.cpp +++ b/src/worldmap/worldmap.cpp @@ -47,7 +47,8 @@ namespace worldmap { WorldMap::WorldMap(const std::string& filename, Savegame& savegame, - const std::string& force_sector, const std::string& force_spawnpoint) : + const std::string& force_sector, const std::string& force_spawnpoint, + const std::string& base_name) : m_sector(nullptr), m_sectors(), m_force_spawnpoint(), @@ -61,21 +62,24 @@ WorldMap::WorldMap(const std::string& filename, Savegame& savegame, m_passive_message_timer(), m_allow_item_pocket(true), m_enter_level(false), + m_really_enter_level(false), m_in_level(false), m_in_world_select(false), m_next_filename(), m_next_force_sector(), m_next_force_spawnpoint() { - load(filename, savegame, force_sector, force_spawnpoint); + load(filename, savegame, force_sector, force_spawnpoint, base_name); } void WorldMap::load(const std::string& filename, Savegame& savegame, - const std::string& force_sector, const std::string& force_spawnpoint) + const std::string& force_sector, const std::string& force_spawnpoint, + const std::string& base_name) { m_map_filename = physfsutil::realpath(filename); m_levels_path = FileSystem::dirname(m_map_filename); + m_base_name = base_name; /* We are reloading, so save now. */ if (m_has_next_worldmap) m_savegame->get_player_status().last_worldmap = m_map_filename; @@ -158,15 +162,21 @@ void WorldMap::update(float dt_sec, const Controller& controller) { if (m_in_world_select) return; - + process_input(controller); + + if (m_really_enter_level) + { + m_enter_level = true; + m_really_enter_level = false; + } if (m_in_level) return; if (MenuManager::instance().is_active()) return; if (m_has_next_worldmap) // A worldmap is scheduled to be changed to. { - load(m_next_filename, *m_savegame, m_next_force_sector, m_next_force_spawnpoint); + load(m_next_filename, *m_savegame, m_next_force_sector, m_next_force_spawnpoint, m_base_name); m_has_next_worldmap = false; return; } @@ -177,7 +187,8 @@ WorldMap::update(float dt_sec, const Controller& controller) void WorldMap::process_input(const Controller& controller) { - m_enter_level = false; + if (!m_really_enter_level) + m_enter_level = false; if (controller.pressed(Control::ACTION) && !m_in_level) { @@ -360,4 +371,10 @@ WorldMap::get_filename() const return m_map_filename; } +const std::string +WorldMap::get_basename() const +{ + return m_base_name; +} + } // namespace worldmap diff --git a/src/worldmap/worldmap.hpp b/src/worldmap/worldmap.hpp index ad4782e956..c8b96e848e 100644 --- a/src/worldmap/worldmap.hpp +++ b/src/worldmap/worldmap.hpp @@ -43,7 +43,8 @@ class WorldMap final : public Screen, public: WorldMap(const std::string& filename, Savegame& savegame, - const std::string& force_sector = "", const std::string& force_spawnpoint = ""); + const std::string& force_sector = "", const std::string& force_spawnpoint = "", + const std::string& base_name = ""); void setup() override; void leave() override; @@ -93,12 +94,16 @@ class WorldMap final : public Screen, bool perform_full_setup = true); const std::string& get_filename() const; + const std::string get_basename() const; + + inline void start_level() { m_really_enter_level = true; } bool is_item_pocket_allowed() const { return m_allow_item_pocket; } private: void load(const std::string& filename, Savegame& savegame, - const std::string& force_sector = "", const std::string& force_spawnpoint = ""); + const std::string& force_sector = "", const std::string& force_spawnpoint = "", + const std::string& base_name = ""); void process_input(const Controller& controller); @@ -116,6 +121,7 @@ class WorldMap final : public Screen, std::string m_name; std::string m_map_filename; std::string m_levels_path; + std::string m_base_name; /* If true, the worldmap will reload on the next update */ bool m_has_next_worldmap; @@ -125,6 +131,7 @@ class WorldMap final : public Screen, Timer m_passive_message_timer; bool m_allow_item_pocket; + bool m_really_enter_level; bool m_enter_level; bool m_in_level; bool m_in_world_select; From 0ef5d1d7d3a108d449386002fc349d53e79120a3 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 21 Aug 2025 14:52:31 -0400 Subject: [PATCH 065/141] Fix windows build --- src/util/file_system.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/file_system.cpp b/src/util/file_system.cpp index 835a821691..fa010ef091 100644 --- a/src/util/file_system.cpp +++ b/src/util/file_system.cpp @@ -23,7 +23,9 @@ #include #include #include +#ifndef _WIN32 #include +#endif #include #if defined(_WIN32) #include From 0ddf1d0f5988959914ee8c4e770c480a3ed841e5 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 21 Aug 2025 15:06:40 -0400 Subject: [PATCH 066/141] Really fix Windows builds --- src/util/file_system.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/file_system.cpp b/src/util/file_system.cpp index fa010ef091..4468b8a3c6 100644 --- a/src/util/file_system.cpp +++ b/src/util/file_system.cpp @@ -245,7 +245,7 @@ void open_editor(const std::string& filename) { #if defined(_WIN32) || defined(_WIN64) - ShellExecute(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL); + ShellExecute(NULL, "open", filename.c_str(), NULL, NULL, SW_SHOWNORMAL); #else #if defined(__APPLE__) std::string cmd = "open \"" + filename + "\""; From eb685248bb2a2d19b5dd0ad47aeeb3892f80389f Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 00:10:22 +0300 Subject: [PATCH 067/141] ButtonWidget: Use normal font to render help text --- src/editor/button_widget.cpp | 9 +-------- src/editor/button_widget.hpp | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index f60400c580..db38f9a4b0 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -56,14 +56,7 @@ ButtonWidget::draw(DrawingContext& context) } if (m_hover && !m_help_text.empty()) - { - const auto& font = Resources::control_font; - const auto text_height = font->get_text_height(m_help_text); - const auto text_width = font->get_text_width(m_help_text); - const auto text_rect = Rectf(m_mouse_pos + Vector(32, 32), m_mouse_pos + Vector(32, 32) + Vector(text_width, text_height)); - context.color().draw_filled_rect(text_rect, Color::BLACK, INT_MAX); - context.color().draw_text(font, m_help_text, m_mouse_pos + Vector(32, 32), FontAlignment::ALIGN_LEFT, INT_MAX); - } + context.color().draw_text(Resources::normal_font, m_help_text, m_mouse_pos + Vector(32, 32), FontAlignment::ALIGN_LEFT, INT_MAX); } void diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index 875908e451..9433489204 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -26,7 +26,6 @@ class ButtonWidget : public Widget { -private: public: ButtonWidget(SpritePtr sprite, const Vector& pos, std::function m_sig_click = {}, std::optional sprite_size = std::nullopt); ButtonWidget(const std::string& path, const Vector& pos, std::function callback = {}, std::optional sprite_size = std::nullopt) : From 119f636845a7c1fc6790f61a202b3c7ba894ac99 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 00:57:11 +0300 Subject: [PATCH 068/141] Fix incorrect spawn position based on cursor position with applied zoom --- src/editor/editor.cpp | 24 +++++++----------------- src/editor/editor.hpp | 2 -- src/editor/overlay_widget.cpp | 10 +++++----- src/editor/overlay_widget.hpp | 3 +++ 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index ad00f7e70f..ad331ffb10 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1058,7 +1058,7 @@ Editor::setup() { MenuManager::instance().push_menu(MenuStorage::EDITOR_LEVELSET_SELECT_MENU); } - set_level(nullptr, true); + set_level(nullptr, true); } m_toolbox_widget->setup(); m_layers_widget->setup(); @@ -1128,13 +1128,10 @@ Editor::event(const SDL_Event& ev) switch (ev.key.keysym.sym) { case SDLK_t: - if (m_shift_pressed) - { - std::pair spawn{get_sector()->get_name(), get_abs_mouse_pos()}; - test_level(spawn); - } - else - test_level(std::nullopt); + if (m_shift_pressed) + test_level(std::pair(get_sector()->get_name(), m_overlay_widget->get_sector_pos())); + else + test_level(std::nullopt); break; case SDLK_s: save_level(); @@ -1145,9 +1142,9 @@ Editor::event(const SDL_Event& ev) case SDLK_y: redo(); break; - case SDLK_x: + case SDLK_x: toggle_tile_object_mode(); - break; + break; case SDLK_PLUS: // Zoom in case SDLK_KP_PLUS: m_new_scale = m_sector->get_camera().get_current_scale() + CAMERA_ZOOM_SENSITIVITY; @@ -1197,13 +1194,6 @@ Editor::event(const SDL_Event& ev) } } -Vector -Editor::get_abs_mouse_pos() -{ - Camera& camera = m_sector->get_camera(); - return camera.get_position() + m_mouse_pos; -} - void Editor::toggle_tile_object_mode() { diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 67a7c1fedc..21f63c0bf8 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -118,8 +118,6 @@ class Editor final : public Screen, m_levelfile = levelfile; m_reload_request = true; } - - Vector get_abs_mouse_pos(); std::string get_level_directory() const; diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 16359276bc..8d0c708782 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -525,7 +525,7 @@ EditorOverlayWidget::hover_object() for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) { const Rectf& bbox = moving_object.get_bbox(); - if (bbox.contains(m_sector_pos - Vector(200.f, 32.f))) + if (bbox.contains(m_sector_pos)) { if (&moving_object != m_hovered_object) { @@ -922,7 +922,7 @@ EditorOverlayWidget::add_path_node() m_edited_path->save_state(); Path::Node new_node(&m_edited_path->get_path()); - new_node.position = m_sector_pos - Vector(200.f, 32.f); + new_node.position = m_sector_pos; new_node.bezier_before = new_node.position; new_node.bezier_after = new_node.position; new_node.time = 1; @@ -959,7 +959,7 @@ EditorOverlayWidget::put_object() } else { - auto target_pos = m_sector_pos - Vector(200.f, 32.f); + auto target_pos = m_sector_pos; if (g_config->editor_snap_to_grid) { auto& snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; @@ -1360,9 +1360,9 @@ EditorOverlayWidget::update_pos() if(m_editor.get_sector() == nullptr) return; m_sector_pos = m_mouse_pos / m_editor.get_sector()->get_camera().get_current_scale() + - m_editor.get_sector()->get_camera().get_translation() + Vector(200.f, 32.f); + m_editor.get_sector()->get_camera().get_translation(); - m_hovered_tile = sp_to_tp(m_sector_pos - Vector(200.f, 32.f)); + m_hovered_tile = sp_to_tp(m_sector_pos); if (m_last_hovered_tile != m_hovered_tile) { diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index c7f1ec048f..7a23fd59da 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -75,6 +75,9 @@ class EditorOverlayWidget final : public Widget void draw_tilemap_outer_shading(DrawingContext&); void draw_tilemap_border(DrawingContext&); + + inline Vector get_sector_pos() const { return m_sector_pos; } + private: static bool action_pressed; static bool alt_pressed; From 868b9b295af092ca92a11abdedaf06462bd82e57 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 01:24:22 -0400 Subject: [PATCH 069/141] strip_leading_dirs --- src/util/file_system.cpp | 14 +++++++++++++- src/util/file_system.hpp | 11 +++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/util/file_system.cpp b/src/util/file_system.cpp index 4468b8a3c6..f28c0d7a94 100644 --- a/src/util/file_system.cpp +++ b/src/util/file_system.cpp @@ -84,6 +84,15 @@ void copy(const std::string& source_path, const std::string& target_path) fs::copy_file(source_path, target_path, fs::copy_options::overwrite_existing); } +std::string strip_leading_dirs(std::string filename) +{ + while (filename.size() > 0 && (filename[filename.size()-1] == '/' || filename[filename.size()-1] == '\\')) + { + filename = filename.substr(0, filename.size()-1); + } + return filename; +} + std::string dirname(const std::string& filename) { std::string::size_type p = filename.find_last_of('/'); @@ -95,8 +104,11 @@ std::string dirname(const std::string& filename) return filename.substr(0, p+1); } -std::string basename(const std::string& filename) +std::string basename(std::string filename, bool greedy) { + if (greedy) + filename = strip_leading_dirs(filename); + std::string::size_type p = filename.find_last_of('/'); if (p == std::string::npos) p = filename.find_last_of('\\'); diff --git a/src/util/file_system.hpp b/src/util/file_system.hpp index bbc49edae8..42d7a0ba72 100644 --- a/src/util/file_system.hpp +++ b/src/util/file_system.hpp @@ -35,8 +35,12 @@ void copy(const std::string& source_path, const std::string& target_path); /** returns the path of the directory the file is in */ std::string dirname(const std::string& filename); -/** returns the name of the file */ -std::string basename(const std::string& filename); +/** returns the name of the file + * @param filename The path to get the basename from. + * @param greedy If true, then attempt to strip any slashes from the end first. + * This fixes situations like /some/dir/ where they really meant /some/dir. + */ +std::string basename(std::string path, bool greedy = false); /** Return a path to 'filename' that is relative to 'basedir', e.g. reldir("/levels/juser/level1.stl", "/levels") -> "juser/level1.stl" */ @@ -48,6 +52,9 @@ std::string extension(const std::string& filename); /** remove everything starting from and including the last dot */ std::string strip_extension(const std::string& filename); +/** strip any leading paths, like /some/path///// */ +std::string strip_leading_dirs(std::string filename); + /** normalize filename so that "blup/bla/blo/../../bar" will become "blup/bar" */ std::string normalize(const std::string& filename); From ce146e3ceb8757d369e2e5c46e45ea8b2334a2b0 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 01:25:37 -0400 Subject: [PATCH 070/141] Use correct path for returning from editor; cleanup code --- src/supertux/game_manager.cpp | 64 +++++++++++++++++++-------------- src/supertux/game_manager.hpp | 5 +++ src/supertux/menu/game_menu.cpp | 22 +++++------- src/worldmap/worldmap.cpp | 18 +++------- src/worldmap/worldmap.hpp | 8 ++--- 5 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index f30233a904..589188c956 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -82,38 +82,48 @@ GameManager::start_level(Level* level, ScreenManager::current()->push_screen(std::move(screen)); } -bool -GameManager::start_worldmap(const World& world, const std::string& worldmap_filename, - const std::string& sector, const std::string& spawnpoint) +worldmap::WorldMap* +GameManager::create_worldmap_instance(const World& world, const std::string& worldmap_filename, + const std::string& sector, const std::string& spawnpoint) +try { - try + m_savegame = Savegame::from_current_profile(world.get_basename()); + + auto filename = m_savegame->get_player_status().last_worldmap; + // If we specified a worldmap filename manually, + // this overrides the default choice of "last worldmap". + if (!worldmap_filename.empty()) { - m_savegame = Savegame::from_current_profile(world.get_basename()); - - auto filename = m_savegame->get_player_status().last_worldmap; - // If we specified a worldmap filename manually, - // this overrides the default choice of "last worldmap". - if (!worldmap_filename.empty()) - { - filename = worldmap_filename; - } - - // No "last worldmap" found and no worldmap_filename - // specified. Let's go ahead and use the worldmap - // filename specified in the world. - if (filename.empty()) - { - filename = world.get_worldmap_filename(); - } - - auto worldmap = std::make_unique(filename, *m_savegame, sector, spawnpoint, world.get_basename()); - ScreenManager::current()->push_screen(std::move(worldmap)); + filename = worldmap_filename; } - catch (const std::exception& e) + + // No "last worldmap" found and no worldmap_filename + // specified. Let's go ahead and use the worldmap + // filename specified in the world. + if (filename.empty()) { - log_warning << "Couldn't start worldmap: " << e.what() << std::endl; - return false; + filename = world.get_worldmap_filename(); } + + auto worldmap = new worldmap::WorldMap(filename, *m_savegame, sector, spawnpoint); + return worldmap; +} +catch (const std::exception& e) +{ + log_warning << "Couldn't start worldmap: " << e.what() << std::endl; + return nullptr; +} + +bool +GameManager::start_worldmap(const World& world, const std::string& worldmap_filename, + const std::string& sector, const std::string& spawnpoint) +{ + auto worldmap = std::unique_ptr( + create_worldmap_instance(world, worldmap_filename, sector, spawnpoint)); + if (!worldmap) + return false; + + ScreenManager::current()->push_screen(std::move(worldmap)); return true; } diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 8f30ce37af..6ca2cbd49b 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -28,6 +28,9 @@ class Savegame; class World; class Level; +namespace worldmap { + class WorldMap; +} class GameManager final : public Currenton { @@ -36,6 +39,8 @@ class GameManager final : public Currenton void save(); + worldmap::WorldMap* create_worldmap_instance(const World& world, const std::string& worldmap_filename = "", + const std::string& sector = "", const std::string& spawnpoint = ""); bool start_worldmap(const World& world, const std::string& worldmap_filename = "", const std::string& sector = "", const std::string& spawnpoint = ""); bool start_worldmap(const World& world, const std::string& worldmap_filename, diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index 7f4699606e..41af8eb037 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -31,6 +31,7 @@ #include "supertux/screen_manager.hpp" #include "supertux/sector.hpp" #include "object/player.hpp" +#include "util/file_system.hpp" #include "util/gettext.hpp" #include "worldmap/worldmap.hpp" @@ -61,7 +62,7 @@ GameMenu::GameMenu() : add_entry(MNID_RESETLEVELCHECKPOINT, _("Restart from Checkpoint")); } - if (g_config->developer_mode) + if (g_config->developer_mode && !Editor::is_active()) { add_entry(MNID_EDITLEVEL, _("Edit Level")); } @@ -112,32 +113,27 @@ GameMenu::menu_action(MenuItem& item) //Editor* editor = new Editor(); //editor->disable_testing(); std::string level_file = GameSession::current()->get_level_file(); - std::string return_to = worldmap::WorldMap::current()->get_basename(); + std::string return_to = worldmap::WorldMap::current()->get_levels_path(); ScreenManager::current()->pop_screen(); ScreenManager::current()->pop_screen(); + // We must queue the creation of the level queue or else the currenton gets clobbered ScreenManager::current()->push_screen([level_file, return_to]() { Editor* editor = new Editor(); editor->set_level(level_file); editor->update(0, Controller()); editor->on_exit([return_to]() { + // Same as last comment... This restarts the previous level ScreenManager::current()->push_screen([return_to]() { - std::unique_ptr world = World::from_directory(return_to); - GameManager::current()->m_savegame = Savegame::from_current_profile(world->get_basename()); - auto filename = GameManager::current()->m_savegame->get_player_status().last_worldmap; - if (filename.empty()) - filename = world->get_worldmap_filename(); - auto worldmap = new worldmap::WorldMap( - filename, *GameManager::current()->m_savegame, - "", "", world->get_basename()); - worldmap->start_level(); + // TODO: Move this somewhere else, it is similar to the GameManager::start_worldmap code + // Also, what if the world gets deleted in the middle of editing? + std::unique_ptr world = World::from_directory(FileSystem::strip_leading_dirs(return_to)); + auto worldmap = GameManager::current()->create_worldmap_instance(*world); return worldmap; }); }); return editor; }); - //std::unique_ptr screen(std::move(editor)); - //ScreenManager::current()->push_screen(std::move(screen)); } break; diff --git a/src/worldmap/worldmap.cpp b/src/worldmap/worldmap.cpp index 3a73daa0f7..649d67cf6f 100644 --- a/src/worldmap/worldmap.cpp +++ b/src/worldmap/worldmap.cpp @@ -47,8 +47,7 @@ namespace worldmap { WorldMap::WorldMap(const std::string& filename, Savegame& savegame, - const std::string& force_sector, const std::string& force_spawnpoint, - const std::string& base_name) : + const std::string& force_sector, const std::string& force_spawnpoint) : m_sector(nullptr), m_sectors(), m_force_spawnpoint(), @@ -69,17 +68,15 @@ WorldMap::WorldMap(const std::string& filename, Savegame& savegame, m_next_force_sector(), m_next_force_spawnpoint() { - load(filename, savegame, force_sector, force_spawnpoint, base_name); + load(filename, savegame, force_sector, force_spawnpoint); } void WorldMap::load(const std::string& filename, Savegame& savegame, - const std::string& force_sector, const std::string& force_spawnpoint, - const std::string& base_name) + const std::string& force_sector, const std::string& force_spawnpoint) { m_map_filename = physfsutil::realpath(filename); m_levels_path = FileSystem::dirname(m_map_filename); - m_base_name = base_name; /* We are reloading, so save now. */ if (m_has_next_worldmap) m_savegame->get_player_status().last_worldmap = m_map_filename; @@ -176,7 +173,7 @@ WorldMap::update(float dt_sec, const Controller& controller) if (m_has_next_worldmap) // A worldmap is scheduled to be changed to. { - load(m_next_filename, *m_savegame, m_next_force_sector, m_next_force_spawnpoint, m_base_name); + load(m_next_filename, *m_savegame, m_next_force_sector, m_next_force_spawnpoint); m_has_next_worldmap = false; return; } @@ -370,11 +367,4 @@ WorldMap::get_filename() const { return m_map_filename; } - -const std::string -WorldMap::get_basename() const -{ - return m_base_name; -} - } // namespace worldmap diff --git a/src/worldmap/worldmap.hpp b/src/worldmap/worldmap.hpp index c8b96e848e..c80e4e4a86 100644 --- a/src/worldmap/worldmap.hpp +++ b/src/worldmap/worldmap.hpp @@ -43,8 +43,7 @@ class WorldMap final : public Screen, public: WorldMap(const std::string& filename, Savegame& savegame, - const std::string& force_sector = "", const std::string& force_spawnpoint = "", - const std::string& base_name = ""); + const std::string& force_sector = "", const std::string& force_spawnpoint = ""); void setup() override; void leave() override; @@ -94,7 +93,6 @@ class WorldMap final : public Screen, bool perform_full_setup = true); const std::string& get_filename() const; - const std::string get_basename() const; inline void start_level() { m_really_enter_level = true; } @@ -102,8 +100,7 @@ class WorldMap final : public Screen, private: void load(const std::string& filename, Savegame& savegame, - const std::string& force_sector = "", const std::string& force_spawnpoint = "", - const std::string& base_name = ""); + const std::string& force_sector = "", const std::string& force_spawnpoint = ""); void process_input(const Controller& controller); @@ -121,7 +118,6 @@ class WorldMap final : public Screen, std::string m_name; std::string m_map_filename; std::string m_levels_path; - std::string m_base_name; /* If true, the worldmap will reload on the next update */ bool m_has_next_worldmap; From 2c124c8bc4cf626461b1d133e029f1b781d78831 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 01:33:18 -0400 Subject: [PATCH 071/141] ScreenManager: Don't draw if there are pending actions --- src/supertux/screen_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/supertux/screen_manager.cpp b/src/supertux/screen_manager.cpp index be4fa93cb7..a44795368a 100644 --- a/src/supertux/screen_manager.cpp +++ b/src/supertux/screen_manager.cpp @@ -638,8 +638,8 @@ void ScreenManager::loop_iter() // limit the draw time offset to at most one step. float time_offset = m_speed * speed_multiplier * std::min(elapsed_time, seconds_per_step); - if ((steps > 0 && !m_screen_stack.empty()) - || always_draw) { + if (((steps > 0 && !m_screen_stack.empty()) + || always_draw) && m_actions.empty() || m_screen_fade) { // Draw a frame Compositor compositor(m_video_system, g_config->frame_prediction ? time_offset : 0.0f); draw(compositor, *m_fps_statistics); From 8728fff62edad7ab8830032c71dad51b37be4448 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 01:33:44 -0400 Subject: [PATCH 072/141] Start level again (accidentally removed) --- src/supertux/menu/game_menu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index 41af8eb037..07e48153d1 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -128,6 +128,7 @@ GameMenu::menu_action(MenuItem& item) // Also, what if the world gets deleted in the middle of editing? std::unique_ptr world = World::from_directory(FileSystem::strip_leading_dirs(return_to)); auto worldmap = GameManager::current()->create_worldmap_instance(*world); + worldmap->start_level(); return worldmap; }); }); From d1f49f0190e246c1bb9bf878ba68cf0b3baa6037 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 01:47:59 -0400 Subject: [PATCH 073/141] Skip level intro when returning from editor --- src/supertux/game_session.cpp | 7 ++++--- src/supertux/game_session.hpp | 2 ++ src/worldmap/worldmap.cpp | 1 - src/worldmap/worldmap_sector.cpp | 5 +++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index f87f65c9c8..30e0423cd7 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -305,12 +305,12 @@ GameSession::restart_level(bool after_death, bool preserve_music) //return (-1); } - if (m_levelintro_shown) + if (m_levelintro_shown && !m_skip_intro) { const Vector shrinkpos = get_fade_point(); ScreenManager::current()->set_screen_fade(std::make_unique(shrinkpos, TELEPORT_FADE_TIME, SHRINKFADE_LAYER, ShrinkFade::FADEIN)); } - + if (!preserve_music) { auto& music_object = m_currentsector->get_singleton_by_type(); @@ -539,7 +539,7 @@ GameSession::setup() m_currentsector->get_singleton_by_type().play_music(LEVEL_MUSIC); int total_stats_to_be_collected = m_level->m_stats.m_total_coins + m_level->m_stats.m_total_badguys + m_level->m_stats.m_total_secrets; - if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0) && m_savegame) { + if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0) && m_savegame && !m_skip_intro) { m_levelintro_shown = true; m_active = false; ScreenManager::current()->push_screen(std::make_unique(*m_level, m_best_level_statistics, m_savegame->get_player_status())); @@ -550,6 +550,7 @@ GameSession::setup() const Vector shrinkpos = get_fade_point(); ScreenManager::current()->set_screen_fade(std::make_unique(shrinkpos, TELEPORT_FADE_TIME, SHRINKFADE_LAYER, ShrinkFade::FADEIN)); } + m_skip_intro = false; m_end_seq_started = false; } diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index da497157d6..30f8667d5a 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -138,6 +138,7 @@ class GameSession final : public Screen, void toggle_pause(); void abort_level(); bool is_active() const; + inline void skip_intro() { m_skip_intro = true; } inline Savegame& get_savegame() const { return *m_savegame; } @@ -197,6 +198,7 @@ class GameSession final : public Screen, float m_play_time; /**< total time in seconds that this session ran interactively */ bool m_levelintro_shown; /**< true if the LevelIntro screen was already shown */ + bool m_skip_intro; /**< Manually skipped the intro from outside this class */ int m_coins_at_start; /** How many coins does the player have at the start */ std::vector m_boni_at_start; /** What boni does the player have at the start */ diff --git a/src/worldmap/worldmap.cpp b/src/worldmap/worldmap.cpp index 649d67cf6f..0b87da939b 100644 --- a/src/worldmap/worldmap.cpp +++ b/src/worldmap/worldmap.cpp @@ -165,7 +165,6 @@ WorldMap::update(float dt_sec, const Controller& controller) if (m_really_enter_level) { m_enter_level = true; - m_really_enter_level = false; } if (m_in_level) return; diff --git a/src/worldmap/worldmap_sector.cpp b/src/worldmap/worldmap_sector.cpp index 07dad63343..94901d84f3 100644 --- a/src/worldmap/worldmap_sector.cpp +++ b/src/worldmap/worldmap_sector.cpp @@ -356,6 +356,11 @@ WorldMapSector::update(float dt_sec) std::string levelfile = m_parent.m_levels_path + level_->get_level_filename(); auto game_session = std::make_unique(levelfile, m_parent.get_savegame(), &level_->get_statistics()); + if (m_parent.m_really_enter_level) + { + game_session->skip_intro(); + m_parent.m_really_enter_level = false; + } game_session->restart_level(); // update state and savegame From 52fe715cc043332384e8f12663ce786f15fcf10a Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 01:59:43 -0400 Subject: [PATCH 074/141] Skip level intro in editor --- src/editor/editor.cpp | 4 ++-- src/supertux/game_manager.cpp | 8 ++++++-- src/supertux/game_manager.hpp | 6 ++++-- src/supertux/levelset_screen.cpp | 11 +++++++++-- src/supertux/levelset_screen.hpp | 3 ++- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index ad331ffb10..6ebd8d97df 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -578,7 +578,7 @@ Editor::test_level(const std::optional>& test_pos m_leveltested = true; if ((m_level && m_levelfile.empty()) || m_levelfile == "") { - GameManager::current()->start_level(m_level.get(), test_pos); + GameManager::current()->start_level(m_level.get(), test_pos, true); return; } @@ -602,7 +602,7 @@ Editor::test_level(const std::optional>& test_pos { // TODO: After LevelSetScreen is removed, this should return a boolean indicating whether load was successful. // If not, call reactivate(). - GameManager::current()->start_level(*current_world, backup_filename, test_pos); + GameManager::current()->start_level(*current_world, backup_filename, test_pos, true); } else if (!GameManager::current()->start_worldmap(*current_world, m_autosave_levelfile, test_pos)) { diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index 589188c956..60f409b354 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -51,7 +51,8 @@ GameManager::save() void GameManager::start_level(const World& world, const std::string& level_filename, - const std::optional>& start_pos) + const std::optional>& start_pos, + bool skip_intro) { m_savegame = Savegame::from_current_profile(world.get_basename()); @@ -67,7 +68,8 @@ GameManager::start_level(const World& world, const std::string& level_filename, void GameManager::start_level(Level* level, - const std::optional>& start_pos) + const std::optional>& start_pos, + bool skip_intro) { m_levelstream.str(""); m_levelstream.clear(); @@ -79,6 +81,8 @@ GameManager::start_level(Level* level, screen->set_start_pos(start_pos->first, start_pos->second); } screen->restart_level(); + if (skip_intro) + screen->skip_intro(); ScreenManager::current()->push_screen(std::move(screen)); } diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 6ca2cbd49b..fc3931a963 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -46,8 +46,10 @@ class GameManager final : public Currenton bool start_worldmap(const World& world, const std::string& worldmap_filename, const std::optional>& start_pos); void start_level(const World& world, const std::string& level_filename, - const std::optional>& start_pos = std::nullopt); - void start_level(Level* level, const std::optional>& start_pos = std::nullopt); + const std::optional>& start_pos = std::nullopt, + bool skip_intro = false); + void start_level(Level* level, const std::optional>& start_pos = std::nullopt, + bool skip_intro = false); public: std::unique_ptr m_savegame; diff --git a/src/supertux/levelset_screen.cpp b/src/supertux/levelset_screen.cpp index 36e05625e6..a95c9b5e7c 100644 --- a/src/supertux/levelset_screen.cpp +++ b/src/supertux/levelset_screen.cpp @@ -29,13 +29,15 @@ LevelsetScreen::LevelsetScreen(const std::string& basedir, const std::string& level_filename, Savegame& savegame, - const std::optional>& start_pos) : + const std::optional>& start_pos, + bool skip_intro) : m_basedir(basedir), m_level_filename(level_filename), m_savegame(savegame), m_level_started(false), m_solved(false), - m_start_pos(start_pos) + m_start_pos(start_pos), + m_skip_intro(skip_intro) { Levelset levelset(basedir); for (int i = 0; i < levelset.get_num_levels(); ++i) @@ -86,6 +88,11 @@ LevelsetScreen::setup() } else { auto screen = std::make_unique(FileSystem::join(m_basedir, m_level_filename), m_savegame); + if (m_skip_intro) + { + screen->skip_intro(); + m_skip_intro = false; + } if (m_start_pos) { screen->set_start_pos(m_start_pos->first, m_start_pos->second); } diff --git a/src/supertux/levelset_screen.hpp b/src/supertux/levelset_screen.hpp index b3c2a4e76f..31ade67c33 100644 --- a/src/supertux/levelset_screen.hpp +++ b/src/supertux/levelset_screen.hpp @@ -37,7 +37,7 @@ class LevelsetScreen final : public Screen, public: LevelsetScreen(const std::string& basedir, const std::string& level_filename, Savegame& savegame, - const std::optional>& start_pos); + const std::optional>& start_pos, bool skip_intro = false); virtual void draw(Compositor& compositor) override; virtual void update(float dt_sec, const Controller& controller) override; @@ -51,6 +51,7 @@ class LevelsetScreen final : public Screen, private: std::optional> m_start_pos; + bool m_skip_intro; LevelsetScreen(const LevelsetScreen&) = delete; LevelsetScreen& operator=(const LevelsetScreen&) = delete; From a46310b5ff49c025b5e19f63bbb7cb3fed3d04c1 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 02:00:52 -0400 Subject: [PATCH 075/141] Really skip level intro in editor --- src/supertux/game_manager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index 60f409b354..2fe931409b 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -59,7 +59,8 @@ GameManager::start_level(const World& world, const std::string& level_filename, auto screen = std::make_unique(world.get_basedir(), level_filename, *m_savegame, - start_pos); + start_pos, + skip_intro); ScreenManager::current()->push_screen(std::move(screen)); if (!Editor::current()) From ce32cf3a42880cd114f623f0760d8788f87c521f Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 02:05:23 -0400 Subject: [PATCH 076/141] Fix tilemap shading when zoomed --- src/editor/overlay_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 8d0c708782..b5704fae17 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -1592,7 +1592,7 @@ EditorOverlayWidget::draw_tilemap_outer_shading(DrawingContext& context) const Color& bg_color = { 0, 0, 0, 0.15 }; const Camera& camera = m_editor.get_sector()->get_camera(); float w_l = (-camera.get_x()) * camera.get_current_scale(); - float height = (camera.get_screen_height()) * camera.get_current_scale(); + float height = camera.get_screen_height(); float w_r = (current_tm->get_width() - camera.get_x()) * camera.get_current_scale(); // Left context.color().draw_filled_rect({0,0,start.x,height}, bg_color, current_tm->get_layer()); From 8bf039f531183a3b9e10fd1d4cffcfa80f2a9583 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 02:11:19 -0400 Subject: [PATCH 077/141] Fix starting editor on Windows/mac(?) --- src/util/file_system.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/util/file_system.cpp b/src/util/file_system.cpp index f28c0d7a94..5fbfccb074 100644 --- a/src/util/file_system.cpp +++ b/src/util/file_system.cpp @@ -256,13 +256,12 @@ void open_path(const std::string& path) void open_editor(const std::string& filename) { -#if defined(_WIN32) || defined(_WIN64) - ShellExecute(NULL, "open", filename.c_str(), NULL, NULL, SW_SHOWNORMAL); -#else - #if defined(__APPLE__) - std::string cmd = "open \"" + filename + "\""; - #else - const char* default_editor = getenv("EDITOR"); + const char* default_editor = +# if defined(_WIN32) || defined(_WIN64) + nullptr; +# else + getenv("EDITOR"); +# endif std::string cmd; if (!g_config->preferred_text_editor.empty()) cmd = g_config->preferred_text_editor + " \"" + filename + "\" &"; @@ -270,7 +269,6 @@ open_editor(const std::string& filename) { cmd = std::string(default_editor) + " \"" + filename + "\" &"; } - #endif int ret = system(cmd.c_str()); if (ret < 0) @@ -281,7 +279,6 @@ open_editor(const std::string& filename) { log_fatal << "error " << ret << " while executing: " << cmd << std::endl; } -#endif } std::string escape_url(const std::string& url) From 83402baf113a78b389b7befa1cbd20addace7aca Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 03:34:03 -0400 Subject: [PATCH 078/141] Fix Edit level button still showing when in editor --- src/supertux/menu/game_menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index 07e48153d1..3077beda76 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -62,7 +62,7 @@ GameMenu::GameMenu() : add_entry(MNID_RESETLEVELCHECKPOINT, _("Restart from Checkpoint")); } - if (g_config->developer_mode && !Editor::is_active()) + if (g_config->developer_mode && !Editor::current()) { add_entry(MNID_EDITLEVEL, _("Edit Level")); } From 50d7678cb93526839e734ce3d50207549984e2e3 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 04:07:20 -0400 Subject: [PATCH 079/141] Return of bullshit --- src/editor/overlay_widget.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index b5704fae17..4567244196 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -525,7 +525,7 @@ EditorOverlayWidget::hover_object() for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) { const Rectf& bbox = moving_object.get_bbox(); - if (bbox.contains(m_sector_pos)) + if (bbox.contains(m_sector_pos - Vector(200.f, 32.f))) { if (&moving_object != m_hovered_object) { @@ -922,7 +922,7 @@ EditorOverlayWidget::add_path_node() m_edited_path->save_state(); Path::Node new_node(&m_edited_path->get_path()); - new_node.position = m_sector_pos; + new_node.position = m_sector_pos - Vector(200.f, 32.f); new_node.bezier_before = new_node.position; new_node.bezier_after = new_node.position; new_node.time = 1; @@ -959,7 +959,7 @@ EditorOverlayWidget::put_object() } else { - auto target_pos = m_sector_pos; + auto target_pos = m_sector_pos - Vector(200.f, 32.f); if (g_config->editor_snap_to_grid) { auto& snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; @@ -1360,9 +1360,9 @@ EditorOverlayWidget::update_pos() if(m_editor.get_sector() == nullptr) return; m_sector_pos = m_mouse_pos / m_editor.get_sector()->get_camera().get_current_scale() + - m_editor.get_sector()->get_camera().get_translation(); + m_editor.get_sector()->get_camera().get_translation() + Vector(200.f, 32.f); - m_hovered_tile = sp_to_tp(m_sector_pos); + m_hovered_tile = sp_to_tp(m_sector_pos - Vector(200.f, 32.f)); if (m_last_hovered_tile != m_hovered_tile) { From 6070e9ca16d2f8767ad2f2dda300903cac97e470 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 13:58:00 +0300 Subject: [PATCH 080/141] Revert "Return of bullshit" This reverts commit 50d7678cb93526839e734ce3d50207549984e2e3. --- src/editor/overlay_widget.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 4567244196..b5704fae17 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -525,7 +525,7 @@ EditorOverlayWidget::hover_object() for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) { const Rectf& bbox = moving_object.get_bbox(); - if (bbox.contains(m_sector_pos - Vector(200.f, 32.f))) + if (bbox.contains(m_sector_pos)) { if (&moving_object != m_hovered_object) { @@ -922,7 +922,7 @@ EditorOverlayWidget::add_path_node() m_edited_path->save_state(); Path::Node new_node(&m_edited_path->get_path()); - new_node.position = m_sector_pos - Vector(200.f, 32.f); + new_node.position = m_sector_pos; new_node.bezier_before = new_node.position; new_node.bezier_after = new_node.position; new_node.time = 1; @@ -959,7 +959,7 @@ EditorOverlayWidget::put_object() } else { - auto target_pos = m_sector_pos - Vector(200.f, 32.f); + auto target_pos = m_sector_pos; if (g_config->editor_snap_to_grid) { auto& snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; @@ -1360,9 +1360,9 @@ EditorOverlayWidget::update_pos() if(m_editor.get_sector() == nullptr) return; m_sector_pos = m_mouse_pos / m_editor.get_sector()->get_camera().get_current_scale() + - m_editor.get_sector()->get_camera().get_translation() + Vector(200.f, 32.f); + m_editor.get_sector()->get_camera().get_translation(); - m_hovered_tile = sp_to_tp(m_sector_pos - Vector(200.f, 32.f)); + m_hovered_tile = sp_to_tp(m_sector_pos); if (m_last_hovered_tile != m_hovered_tile) { From 365812576fc19e254cf3b78a669a6acbc3fb015d Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:33:44 +0300 Subject: [PATCH 081/141] Editor: Improve properties panel code, show position/size related options only, even if hidden by default Removes dynamic_casts and adds a virtual method to `BaseObjectOption` for creating the interface control. Additionally, position/size related options are now shown on the properties panel, despite being hidden in the options menu. They can allow for more precise positioning/scaling. TODO: Support the remainder of `ObjectOptions` as interface controls, improve undo/redo with the properties panel. --- src/editor/editor.cpp | 11 +- src/editor/editor.hpp | 9 +- src/editor/object_option.cpp | 226 +++++++++++++++++++++++- src/editor/object_option.hpp | 37 +++- src/editor/object_settings.cpp | 4 +- src/editor/object_settings.hpp | 3 +- src/editor/overlay_widget.cpp | 135 ++------------ src/editor/particle_editor.cpp | 44 ++--- src/interface/control.cpp | 9 +- src/interface/control.hpp | 5 +- src/interface/control_button.cpp | 6 +- src/interface/control_checkbox.cpp | 7 +- src/interface/control_enum.hpp | 11 +- src/interface/control_textbox.cpp | 4 +- src/interface/control_textbox_float.cpp | 4 +- src/interface/control_textbox_int.cpp | 4 +- src/object/background.cpp | 4 +- src/object/bicycle_platform.cpp | 4 +- src/object/firefly.cpp | 2 +- src/object/pneumatic_platform.cpp | 4 +- src/object/scripted_object.cpp | 4 +- src/object/spawnpoint.cpp | 2 +- src/supertux/moving_object.cpp | 8 +- src/worldmap/spawn_point.cpp | 2 +- src/worldmap/worldmap_object.cpp | 4 +- 25 files changed, 350 insertions(+), 203 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 6ebd8d97df..8b66bf5cdd 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -1471,17 +1471,18 @@ Editor::pack_addon() } void -Editor::addControl(const std::string& name, std::unique_ptr new_control, const std::string& description) +Editor::add_control(const std::string& name, std::unique_ptr new_control, const std::string& description) { + assert(new_control); if (!g_config->editor_show_properties_sidebar) return; + float height = 35.f; - for (const auto& control : m_controls) { + for (const auto& control : m_controls) height = std::max(height, control->get_rect().get_bottom() + 5.f); - } auto control_rect = new_control.get()->get_rect(); - Rectf target_rect = Rectf(); + Rectf target_rect; if (control_rect.get_width() == 0.f || control_rect.get_height() == 0.f) { target_rect = Rectf(100.f, height, 200.f - 1.0f, height + 20.f); @@ -1495,6 +1496,6 @@ Editor::addControl(const std::string& name, std::unique_ptr ne auto dimensions = Rectf(3.f, height, 100.f, height + 20.f); new_control.get()->m_label = std::make_unique(dimensions, std::move(name), std::move(description)); - //new_control.get()->m_on_change = std::function([this](){ this->push_version(); }); + //new_control.get()->m_on_change_callbacks.emplace_back([this](){ this->push_version(); }); m_controls.push_back(std::move(new_control)); } diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 21f63c0bf8..11d667cada 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -83,11 +83,6 @@ class Editor final : public Screen, void event(const SDL_Event& ev) override; void on_window_resize() override; - std::vector>& get_controls() - { - return m_controls; - } - void disable_keyboard() { m_enabled = false; } inline Level* get_level() const { return m_level.get(); } @@ -182,7 +177,9 @@ class Editor final : public Screen, void queue_layers_refresh(); - void addControl(const std::string& name, std::unique_ptr new_control, const std::string& description = ""); + void add_control(const std::string& name, std::unique_ptr new_control, const std::string& description = ""); + inline void clear_controls() { m_controls.clear(); } + void retoggle_undo_tracking(); void undo_stack_cleanup(); diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 92fe2534c2..97a12ae4d4 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -22,11 +22,19 @@ #include #include +#include "editor/editor.hpp" #include "editor/object_menu.hpp" #include "gui/item_stringselect.hpp" #include "gui/menu.hpp" #include "gui/menu_manager.hpp" #include "gui/menu_object_select.hpp" +#include "gui/menu_script.hpp" +#include "interface/control_button.hpp" +#include "interface/control_checkbox.hpp" +#include "interface/control_enum.hpp" +#include "interface/control_textbox.hpp" +#include "interface/control_textbox_float.hpp" +#include "interface/control_textbox_int.hpp" #include "object/tilemap.hpp" #include "supertux/direction.hpp" #include "supertux/game_object_factory.hpp" @@ -139,6 +147,15 @@ BoolObjectOption::add_to_menu(Menu& menu) const menu.add_toggle(-1, get_text(), m_value_pointer); } +std::unique_ptr +BoolObjectOption::create_interface_control() const +{ + auto checkbox = std::make_unique(); + checkbox->set_rect(Rectf(140.f, 0.f, 160.f, 20.f)); + checkbox->bind_value(m_value_pointer); + return checkbox; +} + void BoolObjectOption::parse(const ReaderMapping& reader) { @@ -201,6 +218,15 @@ IntObjectOption::add_to_menu(Menu& menu) const menu.add_intfield(get_text(), m_value_pointer); } +std::unique_ptr +IntObjectOption::create_interface_control() const +{ + auto textbox = std::make_unique(); + textbox->set_rect(Rectf(0, 32, 200, 32)); + textbox->bind_value(m_value_pointer); + return textbox; +} + LabelObjectOption::LabelObjectOption(const std::string& text, unsigned int flags) : ObjectOption(text, "", flags) @@ -219,6 +245,12 @@ LabelObjectOption::add_to_menu(Menu& menu) const menu.add_label(m_text); } +std::unique_ptr +LabelObjectOption::create_interface_control() const +{ + return nullptr; +} + FloatObjectOption::FloatObjectOption(const std::string& text, float* pointer, const std::string& key, std::optional default_value, unsigned int flags) : @@ -257,6 +289,15 @@ FloatObjectOption::add_to_menu(Menu& menu) const menu.add_floatfield(get_text(), m_value_pointer); } +std::unique_ptr +FloatObjectOption::create_interface_control() const +{ + auto textbox = std::make_unique(); + textbox->set_rect(Rectf(0, 32, 200, 32)); + textbox->bind_value(m_value_pointer); + return textbox; +} + StringObjectOption::StringObjectOption(const std::string& text, std::string* pointer, const std::string& key, std::optional default_value, unsigned int flags) : @@ -297,6 +338,15 @@ StringObjectOption::add_to_menu(Menu& menu) const menu.add_textfield(get_text(), m_value_pointer); } +std::unique_ptr +StringObjectOption::create_interface_control() const +{ + auto textbox = std::make_unique(); + textbox->set_rect(Rectf(0, 32, 200, 32)); + textbox->bind_string(m_value_pointer); + return textbox; +} + StringMultilineObjectOption::StringMultilineObjectOption(const std::string& text, std::string* pointer, const std::string& key, std::optional default_value, unsigned int flags) : @@ -340,6 +390,17 @@ StringMultilineObjectOption::add_to_menu(Menu& menu) const menu.add_script(m_key, get_text(), m_value_pointer); } +std::unique_ptr +StringMultilineObjectOption::create_interface_control() const +{ + auto button = std::make_unique(_("Edit...")); + button->m_on_change_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { + MenuManager::instance().push_menu(std::make_unique(key, value_ptr)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; +} + StringSelectObjectOption::StringSelectObjectOption(const std::string& text, int* pointer, const std::vector& select, std::optional default_value, @@ -371,11 +432,11 @@ StringSelectObjectOption::save(Writer& writer) const std::string StringSelectObjectOption::to_string() const { - int* selected_id = static_cast(m_value_pointer); - if (*selected_id >= int(m_select.size()) || *selected_id < 0) { + int& selected_id = *m_value_pointer; + if (selected_id >= int(m_select.size()) || selected_id < 0) { return _("invalid"); //Test whether the selected ID is valid } else { - return m_select[*selected_id]; + return m_select[selected_id]; } } @@ -389,6 +450,23 @@ StringSelectObjectOption::add_to_menu(Menu& menu) const menu.add_string_select(-1, get_text(), m_value_pointer, m_select); } +std::unique_ptr +StringSelectObjectOption::create_interface_control() const +{ + auto dropdown = std::make_unique>(); + for (int i = 0; i < static_cast(m_select.size()); ++i) + { + dropdown->add_option(i, m_select[i]); + } + + int& selected_id = *m_value_pointer; + if (selected_id >= static_cast(m_select.size()) || selected_id < 0) + selected_id = 0; // Set the option to zero when not selectable + + dropdown->bind_value(m_value_pointer); + return dropdown; +} + EnumObjectOption::EnumObjectOption(const std::string& text, int* pointer, const std::vector& labels, const std::vector& symbols, @@ -449,6 +527,17 @@ EnumObjectOption::add_to_menu(Menu& menu) const menu.add_string_select(-1, get_text(), m_value_pointer, m_labels); } +std::unique_ptr +EnumObjectOption::create_interface_control() const +{ + auto dropdown = std::make_unique>(); + for (int i = 0; i < m_labels.size(); i++) + { + dropdown->add_option(i, m_labels[i]); + } + dropdown->bind_value(m_value_pointer); + return dropdown; +} ScriptObjectOption::ScriptObjectOption(const std::string& text, std::string* pointer, const std::string& key, unsigned int flags) : @@ -489,6 +578,17 @@ ScriptObjectOption::add_to_menu(Menu& menu) const menu.add_script(m_key, get_text(), m_value_pointer); } +std::unique_ptr +ScriptObjectOption::create_interface_control() const +{ + auto button = std::make_unique(_("Edit...")); + button->m_on_change_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { + MenuManager::instance().push_menu(std::make_unique(key, value_ptr)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; +} + FileObjectOption::FileObjectOption(const std::string& text, std::string* pointer, std::optional default_value, const std::string& key, @@ -538,6 +638,19 @@ FileObjectOption::add_to_menu(Menu& menu) const menu.add_file(get_text(), m_value_pointer, m_filter, m_basedir, m_path_relative_to_basedir); } +std::unique_ptr +FileObjectOption::create_interface_control() const +{ + auto button = std::make_unique(_("Browse...")); + button->m_on_change_callbacks.emplace_back( + [input = m_value_pointer, extensions = m_filter, basedir = m_basedir, path_relative_to_basedir = m_path_relative_to_basedir]() + { + MenuManager::instance().push_menu(std::make_unique(input, extensions, basedir, path_relative_to_basedir)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; +} + ColorObjectOption::ColorObjectOption(const std::string& text, Color* pointer, const std::string& key, std::optional default_value, bool use_alpha, unsigned int flags) : @@ -583,6 +696,12 @@ ColorObjectOption::add_to_menu(Menu& menu) const menu.add_color(get_text(), m_value_pointer); } +std::unique_ptr +ColorObjectOption::create_interface_control() const +{ + return nullptr; +} + ObjectSelectObjectOption::ObjectSelectObjectOption(const std::string& text, std::vector>* pointer, uint8_t get_objects_param, const std::function)>& add_object_func, const std::string& key, unsigned int flags) : @@ -622,7 +741,8 @@ ObjectSelectObjectOption::parse(const ReaderMapping& reader) void ObjectSelectObjectOption::save(Writer& writer) const -{ +{MenuManager::instance().push_menu(std::make_unique(input, extensions, basedir, + path_relative_to_basedir, nullptr, item_processor)); if (get_key().empty()) return; @@ -662,6 +782,12 @@ ObjectSelectObjectOption::add_to_menu(Menu& menu) const }); } +std::unique_ptr +ObjectSelectObjectOption::create_interface_control() const +{ + return nullptr; +} + TilesObjectOption::TilesState::TilesState() : width(), height(), @@ -699,6 +825,12 @@ TilesObjectOption::add_to_menu(Menu& menu) const { } +std::unique_ptr +TilesObjectOption::create_interface_control() const +{ + return nullptr; +} + void TilesObjectOption::save_state() { @@ -802,6 +934,12 @@ PathObjectOption::add_to_menu(Menu& menu) const { } +std::unique_ptr +PathObjectOption::create_interface_control() const +{ + return nullptr; +} + PathRefObjectOption::PathRefObjectOption(const std::string& text, PathObject& target, const std::string& path_ref, const std::string& key, unsigned int flags) : ObjectOption(text, key, flags, &target), @@ -835,6 +973,12 @@ PathRefObjectOption::add_to_menu(Menu& menu) const menu.add_path_settings(m_text, *m_value_pointer, m_path_ref); } +std::unique_ptr +PathRefObjectOption::create_interface_control() const +{ + return nullptr; +} + SExpObjectOption::SExpObjectOption(const std::string& text, const std::string& key, sexp::Value& value, unsigned int flags) : ObjectOption(text, key, flags, &value) @@ -866,6 +1010,12 @@ SExpObjectOption::add_to_menu(Menu& menu) const { } +std::unique_ptr +SExpObjectOption::create_interface_control() const +{ + return nullptr; +} + PathHandleOption::PathHandleOption(const std::string& text, PathWalker::Handle& handle, const std::string& key, unsigned int flags) : ObjectOption(text, key, flags), @@ -916,6 +1066,12 @@ PathHandleOption::add_to_menu(Menu& menu) const menu.add_floatfield(get_text() + " (" + _("Offset Y") + ")", &m_target.m_pixel_offset.y); } +std::unique_ptr +PathHandleOption::create_interface_control() const +{ + return nullptr; +} + RemoveObjectOption::RemoveObjectOption() : ObjectOption(_("Remove"), "", 0) { @@ -933,8 +1089,14 @@ RemoveObjectOption::add_to_menu(Menu& menu) const menu.add_entry(ObjectMenu::MNID_REMOVE, get_text()); } -TestFromHereOption::TestFromHereOption() : - ObjectOption(_("Test from here"), "", 0) +std::unique_ptr +RemoveObjectOption::create_interface_control() const +{ + return nullptr; +} + +TestFromHereOption::TestFromHereOption(const MovingObject* object_ptr) : + ObjectOption(_("Test from here"), "", 0, object_ptr) { } @@ -950,6 +1112,22 @@ TestFromHereOption::add_to_menu(Menu& menu) const menu.add_entry(ObjectMenu::MNID_TEST_FROM_HERE, get_text()); } +std::unique_ptr +TestFromHereOption::create_interface_control() const +{ + auto button = std::make_unique(_("Test")); + button->set_rect(Rectf(0, 32, 200, 32)); + button->m_on_change_callbacks.emplace_back([object_ptr = m_value_pointer]() { + Editor& editor = *Editor::current(); + // TODO: Pressing the return key from within a game session automatically + // triggers this button again if it's previously been pushed. This needs + // to get fixed. + editor.m_test_pos = std::make_pair(editor.get_sector()->get_name(), object_ptr->get_pos()); + editor.m_test_request = true; + }); + return button; +} + ParticleEditorOption::ParticleEditorOption() : ObjectOption(_("Open Particle Editor"), "", 0) { @@ -967,6 +1145,12 @@ ParticleEditorOption::add_to_menu(Menu& menu) const menu.add_entry(ObjectMenu::MNID_OPEN_PARTICLE_EDITOR, get_text()); } +std::unique_ptr +ParticleEditorOption::create_interface_control() const +{ + return nullptr; +} + ButtonOption::ButtonOption(const std::string& text, std::function callback) : ObjectOption(text, "", 0), m_callback(std::move(callback)) @@ -985,6 +1169,12 @@ ButtonOption::add_to_menu(Menu& menu) const menu.add_entry(get_text(), m_callback); } +std::unique_ptr +ButtonOption::create_interface_control() const +{ + return nullptr; +} + StringArrayOption::StringArrayOption(const std::string& text, const std::string& key, std::vector& items) : ObjectOption(text, key, 0), m_items(items) @@ -1008,6 +1198,12 @@ StringArrayOption::add_to_menu(Menu& menu) const menu.add_string_array(get_text(), m_items); } +std::unique_ptr +StringArrayOption::create_interface_control() const +{ + return nullptr; +} + ListOption::ListOption(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr) : ObjectOption(text, key, 0, value_ptr), m_items(items) @@ -1031,6 +1227,12 @@ ListOption::add_to_menu(Menu& menu) const menu.add_list(get_text(), m_items, m_value_pointer); } +std::unique_ptr +ListOption::create_interface_control() const +{ + return nullptr; +} + DirectionOption::DirectionOption(const std::string& text, Direction* value_ptr, std::vector possible_directions, const std::string& key, unsigned int flags) : @@ -1084,3 +1286,15 @@ DirectionOption::add_to_menu(Menu& menu) const *value_ptr = possible_directions.at(index); }); } + +std::unique_ptr +DirectionOption::create_interface_control() const +{ + auto dropdown = std::make_unique>(); + for (const auto& direction : m_possible_directions) + { + dropdown->add_option(direction, dir_to_translated_string(direction)); + } + dropdown->bind_value(m_value_pointer); + return dropdown; +} diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index 56c45af433..52b76a2cb1 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -31,8 +31,12 @@ enum ObjectOptionFlag { shouldn't be exposed to the user */ OPTION_HIDDEN = (1 << 0), + /** Set if the value should be hidden in ObjectMenu, but visible + on the properties sidebar */ + OPTION_VISIBLE_PROPERTIES = (1 << 1), + /** Set if the text should be saved as translatable */ - OPTION_TRANSLATABLE = (1 << 1) + OPTION_TRANSLATABLE = (1 << 2) }; namespace sexp { @@ -41,7 +45,9 @@ class Value; class Color; enum class Direction; class GameObject; +class InterfaceControl; class Menu; +class MovingObject; class Path; class PathObject; class ReaderMapping; @@ -63,6 +69,7 @@ class BaseObjectOption virtual void save(Writer& writer) const = 0; virtual std::string to_string() const = 0; virtual void add_to_menu(Menu& menu) const = 0; + virtual std::unique_ptr create_interface_control() const = 0; std::string save() const; @@ -120,6 +127,7 @@ class BoolObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: const std::optional m_default_value; @@ -140,6 +148,7 @@ class IntObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: const std::optional m_default_value; @@ -159,6 +168,7 @@ class LabelObjectOption final : public ObjectOption<> virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: LabelObjectOption(const LabelObjectOption&) = delete; @@ -176,6 +186,7 @@ class FloatObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: const std::optional m_default_value; @@ -196,6 +207,7 @@ class StringObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: std::optional m_default_value; @@ -216,6 +228,7 @@ class StringMultilineObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: std::optional m_default_value; @@ -236,6 +249,7 @@ class StringSelectObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: const std::vector m_select; @@ -259,6 +273,7 @@ class EnumObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; const std::vector& get_labels() const { return m_labels; } const std::vector& get_symbols() const { return m_symbols; } @@ -284,6 +299,7 @@ class ScriptObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: ScriptObjectOption(const ScriptObjectOption&) = delete; @@ -305,6 +321,7 @@ class FileObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: std::optional m_default_value; @@ -328,6 +345,7 @@ class ColorObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: const std::optional m_default_value; @@ -349,6 +367,7 @@ class ObjectSelectObjectOption final : public ObjectOption create_interface_control() const override; private: uint8_t m_get_objects_param; @@ -369,6 +388,7 @@ class TilesObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; virtual void save_state() override; virtual void parse_state(const ReaderMapping& reader) override; @@ -404,6 +424,7 @@ class PathObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: PathObjectOption(const PathObjectOption&) = delete; @@ -420,6 +441,7 @@ class PathRefObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: std::string m_path_ref; @@ -438,6 +460,7 @@ class SExpObjectOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: SExpObjectOption(const SExpObjectOption&) = delete; @@ -454,6 +477,7 @@ class PathHandleOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: PathWalker::Handle& m_target; @@ -472,21 +496,23 @@ class RemoveObjectOption final : public ObjectOption<> virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: RemoveObjectOption(const RemoveObjectOption&) = delete; RemoveObjectOption& operator=(const RemoveObjectOption&) = delete; }; -class TestFromHereOption final : public ObjectOption<> +class TestFromHereOption final : public ObjectOption { public: - TestFromHereOption(); + TestFromHereOption(const MovingObject* object_ptr); virtual void parse(const ReaderMapping& reader) override {} virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: TestFromHereOption(const TestFromHereOption&) = delete; @@ -502,6 +528,7 @@ class ParticleEditorOption final : public ObjectOption<> virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: ParticleEditorOption(const ParticleEditorOption&) = delete; @@ -517,6 +544,7 @@ class ButtonOption final : public ObjectOption<> virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: std::function m_callback; @@ -535,6 +563,7 @@ class StringArrayOption final : public ObjectOption<> virtual void save(Writer& writer) const override; virtual std::string to_string() const override { return "text-area"; } virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: std::vector& m_items; @@ -553,6 +582,7 @@ class ListOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override { return *m_value_pointer; } virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; private: const std::vector& m_items; @@ -573,6 +603,7 @@ class DirectionOption final : public ObjectOption virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual std::unique_ptr create_interface_control() const override; const std::vector& get_possible_directions() const { diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index f62ab4b3f8..8d6de9a132 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -352,9 +352,9 @@ ObjectSettings::add_string_array(const std::string& text, const std::string& key } void -ObjectSettings::add_test_from_here() +ObjectSettings::add_test_from_here(const MovingObject* object_ptr) { - add_option(std::make_unique()); + add_option(std::make_unique(object_ptr)); } void diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index d9ceb9d85b..81188eb7fb 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -26,6 +26,7 @@ class Color; enum class Direction; class GameObject; +class MovingObject; class PathObject; class ReaderMapping; enum class WalkMode; @@ -152,7 +153,7 @@ class ObjectSettings final void add_sexp(const std::string& text, const std::string& key, sexp::Value& value, unsigned int flags = 0); void add_string_array(const std::string& text, const std::string& key, std::vector& items); - void add_test_from_here(); + void add_test_from_here(const MovingObject* object_ptr); void add_particle_editor(); void add_path_handle(const std::string& text, PathWalker::Handle& handle, const std::string& key = {}, unsigned int flags = 0); diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index b5704fae17..45d62742ce 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -26,14 +26,8 @@ #include "editor/tile_selection.hpp" #include "editor/tip.hpp" #include "gui/menu.hpp" -#include "gui/menu_script.hpp" #include "gui/menu_manager.hpp" -#include "interface/control_button.hpp" -#include "interface/control_checkbox.hpp" -#include "interface/control_enum.hpp" -#include "interface/control_textbox.hpp" -#include "interface/control_textbox_float.hpp" -#include "interface/control_textbox_int.hpp" +#include "interface/control.hpp" #include "math/bezier.hpp" #include "object/camera.hpp" #include "object/path_gameobject.hpp" @@ -705,94 +699,27 @@ EditorOverlayWidget::show_object_menu(GameObject& object) } void -EditorOverlayWidget::update_properties_panel(GameObject* object) +EditorOverlayWidget::update_properties_panel(GameObject* hovered_object) { - auto& controls = m_editor.get_controls(); - controls.clear(); + m_editor.clear_controls(); - if (object == nullptr) + if (!hovered_object || !g_config->editor_show_properties_sidebar) return; - auto hovered_object = object; + hovered_object->save_state(); ObjectSettings os = hovered_object->get_settings(); - for(const auto& option : os.get_options()) + for (const auto& option : os.get_options()) { - auto text = option.get()->get_text(); - auto description = option.get()->get_description(); - if (dynamic_cast(option.get())) - { - m_editor.addControl(text, nullptr, description); - } - else if (auto int_option = dynamic_cast(option.get())) - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->bind_value(int_option->get_value()); - m_editor.addControl(text, std::move(textbox), description); - } - else if (auto float_option = dynamic_cast(option.get())) - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->bind_value(float_option->get_value()); - m_editor.addControl(text, std::move(textbox), description); - } - else if (auto bool_option = dynamic_cast(option.get())) - { - auto checkbox = std::make_unique(); - checkbox.get()->set_rect(Rectf(140.f, 0.f, 160.f, 20.f)); - checkbox.get()->bind_value(bool_option->get_value()); - m_editor.addControl(text, std::move(checkbox), description); - } - else if (auto script_option = dynamic_cast(option.get())) - { - auto button = std::make_unique(_("Edit...")); - const auto value_ptr = script_option->get_value(); - button.get()->m_on_change = std::function([value_ptr, script_option]() { - MenuManager::instance().push_menu(std::make_unique(script_option->get_key(), value_ptr)); - }); - button.get()->set_rect(Rectf(0, 32, 20, 32)); - m_editor.addControl(text, std::move(button), description); - } - else if (auto string_option = dynamic_cast(option.get())) - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->bind_string(string_option->get_value()); - m_editor.addControl(text, std::move(textbox), description); - } - else if (auto test_from_here_option = dynamic_cast(option.get())) - { - auto button = std::make_unique(_("Test")); - button->set_rect(Rectf(0, 32, 200, 32)); - button.get()->m_on_change = std::function([hovered_object, this]() { - auto spawnpoint = dynamic_cast(hovered_object); - if (spawnpoint == nullptr) - return; + if ((option->get_flags() & OPTION_HIDDEN) && !(option->get_flags() & OPTION_VISIBLE_PROPERTIES)) + continue; - // TODO: Pressing the return key from within a game session automatically - // triggers this button again if it's previously been pushed. This needs - // to get fixed. - auto sector_name = Editor::current()->get_sector()->get_name(); - m_editor.m_test_pos = std::make_pair(sector_name, spawnpoint->get_pos()); - m_editor.m_test_request = true; - }); - m_editor.addControl(text, std::move(button), description); - } - else if (auto enum_option = dynamic_cast(option.get())) - { - auto labels = enum_option->get_labels(); - auto dropdown = std::make_unique>(); - - for(int i = 0; i < labels.size(); i++) - { - dropdown->add_option(i, labels[i]); - } + auto control = option->create_interface_control(); + if (!control) + continue; - dropdown.get()->bind_value(enum_option->get_value()); - dropdown.get()->m_on_change = std::function([hovered_object, this]() { - if (hovered_object == nullptr) + control->m_on_change_callbacks.emplace_back([hovered_object, this]() { + if (!hovered_object) return; // TODO: Updating the object doesn't work every time. // Investigate why this is the case! @@ -800,36 +727,12 @@ EditorOverlayWidget::update_properties_panel(GameObject* object) hovered_object->check_state(); update_properties_panel(hovered_object); }); - m_editor.addControl(text, std::move(dropdown), description); - } - else if(auto direction_option = dynamic_cast(option.get())) - { - auto directions = direction_option->get_possible_directions(); - auto dropdown = std::make_unique>(); - for (const auto& direction : directions) - { - dropdown->add_option(direction, dir_to_translated_string(direction)); - } - dropdown->bind_value(direction_option->get_value()); - dropdown.get()->m_on_change = std::function([hovered_object, this]() { - if (hovered_object == nullptr) - return; - // TODO: Updating the object doesn't work every time. - // Investigate why this is the case! - hovered_object->after_editor_set(); - hovered_object->check_state(); - update_properties_panel(hovered_object); - }); - - m_editor.addControl(text, std::move(dropdown), description); - } - else - { - auto textbox = std::make_unique(); - textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - textbox.get()->put_text(option.get()->to_string()); - m_editor.addControl(text, std::move(textbox), description); - } + m_editor.add_control(option->get_text(), std::move(control), option->get_description()); + + //auto textbox = std::make_unique(); + //textbox.get()->set_rect(Rectf(0, 32, 200, 32)); + //textbox.get()->put_text(option.get()->to_string()); + //m_editor.addControl(text, std::move(textbox), description); } } diff --git a/src/editor/particle_editor.cpp b/src/editor/particle_editor.cpp index 13460c4338..b317a4ba8f 100644 --- a/src/editor/particle_editor.cpp +++ b/src/editor/particle_editor.cpp @@ -112,7 +112,7 @@ ParticleEditor::reset_main_ui() // TODO: Use the addButton() command // Texture button start auto texture_btn = std::make_unique(_("Change texture... ->")); - texture_btn.get()->m_on_change = std::function([this](){ + texture_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_in_texture_tab = true; }); float tmp_height = 0.f; @@ -227,7 +227,7 @@ ParticleEditor::reset_main_ui() // TODO: add some ParticleEditor::addButton() function so that I don't have to put all that in here auto clear_btn = std::make_unique(_("Clear")); - clear_btn.get()->m_on_change = std::function([this](){ m_particles->clear(); }); + clear_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->clear(); }); float height = 0.f; for (const auto& control : m_controls) { height = std::max(height, control->get_rect().get_bottom() + 5.f); @@ -243,7 +243,7 @@ ParticleEditor::reset_texture_ui() m_texture_rebinds.clear(); auto return_btn = std::make_unique(_("<- General settings")); - return_btn.get()->m_on_change = std::function([this](){ + return_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_in_texture_tab = false; }); return_btn.get()->set_rect(Rectf(25.f, 0.f, 325.f, 20.f)); @@ -253,7 +253,7 @@ ParticleEditor::reset_texture_ui() likeliness_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->likeliness)); likeliness_control.get()->set_rect(Rectf(150.f, 50.f, 350.f, 70.f)); likeliness_control.get()->m_label = std::make_unique(Rectf(5.f, 50.f, 135.f, 70.f), _("Likeliness")); - likeliness_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + likeliness_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto likeliness_control_ptr = likeliness_control.get(); m_texture_rebinds.push_back( [this, likeliness_control_ptr]{ likeliness_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->likeliness)); @@ -264,7 +264,7 @@ ParticleEditor::reset_texture_ui() color_r_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->color.red)); color_r_control.get()->set_rect(Rectf(150.f, 80.f, 192.f, 100.f)); color_r_control.get()->m_label = std::make_unique(Rectf(5.f, 80.f, 140.f, 100.f), _("Color (RGBA)")); - color_r_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + color_r_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); color_r_control.get()->m_validate_float = m_clamp_0_1; auto color_r_control_ptr = color_r_control.get(); m_texture_rebinds.push_back( [this, color_r_control_ptr]{ @@ -275,7 +275,7 @@ ParticleEditor::reset_texture_ui() auto color_g_control = std::make_unique(); color_g_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->color.green)); color_g_control.get()->set_rect(Rectf(202.f, 80.f, 245.f, 100.f)); - color_g_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + color_g_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); color_g_control.get()->m_validate_float = m_clamp_0_1; auto color_g_control_ptr = color_g_control.get(); m_texture_rebinds.push_back( [this, color_g_control_ptr]{ @@ -286,7 +286,7 @@ ParticleEditor::reset_texture_ui() auto color_b_control = std::make_unique(); color_b_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->color.blue)); color_b_control.get()->set_rect(Rectf(255.f, 80.f, 297.f, 100.f)); - color_b_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + color_b_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); color_b_control.get()->m_validate_float = m_clamp_0_1; auto color_b_control_ptr = color_b_control.get(); m_texture_rebinds.push_back( [this, color_b_control_ptr]{ @@ -297,7 +297,7 @@ ParticleEditor::reset_texture_ui() auto color_a_control = std::make_unique(); color_a_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->color.alpha)); color_a_control.get()->set_rect(Rectf(307.f, 80.f, 350.f, 100.f)); - color_a_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + color_a_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); color_a_control.get()->m_validate_float = m_clamp_0_1; auto color_a_control_ptr = color_a_control.get(); m_texture_rebinds.push_back( [this, color_a_control_ptr]{ @@ -309,7 +309,7 @@ ParticleEditor::reset_texture_ui() scale_x_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->scale.x)); scale_x_control.get()->set_rect(Rectf(150.f, 110.f, 240.f, 130.f)); scale_x_control.get()->m_label = std::make_unique(Rectf(5.f, 110.f, 150.f, 130.f), _("Scale (x, y)")); - scale_x_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + scale_x_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto scale_x_control_ptr = scale_x_control.get(); m_texture_rebinds.push_back( [this, scale_x_control_ptr]{ scale_x_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->scale.x)); @@ -319,7 +319,7 @@ ParticleEditor::reset_texture_ui() auto scale_y_control = std::make_unique(); scale_y_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->scale.y)); scale_y_control.get()->set_rect(Rectf(260.f, 110.f, 350.f, 130.f)); - scale_y_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + scale_y_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto scale_y_control_ptr = scale_y_control.get(); m_texture_rebinds.push_back( [this, scale_y_control_ptr]{ scale_y_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->scale.y)); @@ -330,7 +330,7 @@ ParticleEditor::reset_texture_ui() hb_scale_x_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_scale.x)); hb_scale_x_control.get()->set_rect(Rectf(150.f, 140.f, 240.f, 160.f)); hb_scale_x_control.get()->m_label = std::make_unique(Rectf(5.f, 140.f, 150.f, 160.f), _("Hitbox scale (x, y)")); - hb_scale_x_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + hb_scale_x_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto hb_scale_x_control_ptr = hb_scale_x_control.get(); m_texture_rebinds.push_back( [this, hb_scale_x_control_ptr]{ hb_scale_x_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_scale.x)); @@ -340,7 +340,7 @@ ParticleEditor::reset_texture_ui() auto hb_scale_y_control = std::make_unique(); hb_scale_y_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_scale.y)); hb_scale_y_control.get()->set_rect(Rectf(260.f, 140.f, 350.f, 160.f)); - hb_scale_y_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + hb_scale_y_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto hb_scale_y_control_ptr = hb_scale_y_control.get(); m_texture_rebinds.push_back( [this, hb_scale_y_control_ptr]{ hb_scale_y_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_scale.y)); @@ -351,7 +351,7 @@ ParticleEditor::reset_texture_ui() hb_offset_x_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_offset.x)); hb_offset_x_control.get()->set_rect(Rectf(150.f, 170.f, 240.f, 190.f)); hb_offset_x_control.get()->m_label = std::make_unique(Rectf(5.f, 170.f, 150.f, 190.f), _("Hitbox offset relative to scale")); - hb_offset_x_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + hb_offset_x_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto hb_offset_x_control_ptr = hb_offset_x_control.get(); m_texture_rebinds.push_back( [this, hb_offset_x_control_ptr]{ hb_offset_x_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_offset.x)); @@ -361,7 +361,7 @@ ParticleEditor::reset_texture_ui() auto hb_offset_y_control = std::make_unique(); hb_offset_y_control.get()->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_offset.y)); hb_offset_y_control.get()->set_rect(Rectf(260.f, 170.f, 350.f, 190.f)); - hb_offset_y_control.get()->m_on_change = std::function([this](){ m_particles->reinit_textures(); this->push_version(); }); + hb_offset_y_control.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->reinit_textures(); this->push_version(); }); auto hb_offset_y_control_ptr = hb_offset_y_control.get(); m_texture_rebinds.push_back( [this, hb_offset_y_control_ptr]{ hb_offset_y_control_ptr->bind_value(&((m_particles->m_textures.begin() + m_texture_current)->hb_offset.y)); @@ -370,7 +370,7 @@ ParticleEditor::reset_texture_ui() // Texture button start auto chg_texture_btn = std::make_unique(_("Change texture...")); - chg_texture_btn.get()->m_on_change = std::function([this](){ + chg_texture_btn.get()->m_on_change_callbacks.emplace_back([this](){ const std::vector& filter = {".jpg", ".png", ".surface"}; MenuManager::instance().push_menu(std::make_unique( nullptr, @@ -389,7 +389,7 @@ ParticleEditor::reset_texture_ui() // Texture button end auto prev_btn = std::make_unique("<"); - prev_btn.get()->m_on_change = std::function([this](){ + prev_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_texture_current--; if (m_texture_current < 0) m_texture_current = 0; for (const auto& refresh : m_texture_rebinds) @@ -399,7 +399,7 @@ ParticleEditor::reset_texture_ui() m_controls_textures.push_back(std::move(prev_btn)); auto del_btn = std::make_unique("-"); - del_btn.get()->m_on_change = std::function([this](){ + del_btn.get()->m_on_change_callbacks.emplace_back([this](){ if (m_particles->m_textures.size() < 1) return; m_particles->m_textures.erase(m_particles->m_textures.begin() + m_texture_current); @@ -414,7 +414,7 @@ ParticleEditor::reset_texture_ui() m_controls_textures.push_back(std::move(del_btn)); auto add_btn = std::make_unique("+"); - add_btn.get()->m_on_change = std::function([this](){ + add_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->m_textures.push_back(CustomParticleSystem::SpriteProperties()); m_texture_current = static_cast(m_particles->m_textures.size()) - 1; for (const auto& refresh : m_texture_rebinds) @@ -426,7 +426,7 @@ ParticleEditor::reset_texture_ui() m_controls_textures.push_back(std::move(add_btn)); auto next_btn = std::make_unique(">"); - next_btn.get()->m_on_change = std::function([this](){ + next_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_texture_current++; if (m_texture_current > static_cast(m_particles->m_textures.size()) - 1) m_texture_current = static_cast(m_particles->m_textures.size()) - 1; @@ -473,8 +473,8 @@ ParticleEditor::addTextboxFloatWithImprecision(const std::string& name, float* b float_control.get()->m_label = std::make_unique(Rectf(5.f, height, 145.f, height + 20.f), name); imp_control.get()->m_label = std::make_unique(Rectf(240.f, height, 260.f, height + 20.f), "±"); - float_control.get()->m_on_change = std::function([this](){ this->push_version(); }); - imp_control.get()->m_on_change = std::function([this](){ this->push_version(); }); + float_control.get()->m_on_change_callbacks.emplace_back([this](){ this->push_version(); }); + imp_control.get()->m_on_change_callbacks.emplace_back([this](){ this->push_version(); }); m_controls.push_back(std::move(float_control)); m_controls.push_back(std::move(imp_control)); @@ -556,7 +556,7 @@ ParticleEditor::addControl(std::string name, std::unique_ptr n } new_control.get()->m_label = std::make_unique(Rectf(5.f, height, 135.f, height + 20.f), std::move(name)); - new_control.get()->m_on_change = std::function([this](){ this->push_version(); }); + new_control.get()->m_on_change_callbacks.emplace_back([this](){ this->push_version(); }); m_controls.push_back(std::move(new_control)); } diff --git a/src/interface/control.cpp b/src/interface/control.cpp index ab69962a89..94b4b2c399 100644 --- a/src/interface/control.cpp +++ b/src/interface/control.cpp @@ -17,10 +17,17 @@ #include "interface/control.hpp" InterfaceControl::InterfaceControl() : - m_on_change(), + m_on_change_callbacks(), m_label(), m_has_focus(), m_rect(), m_parent(nullptr) { } + +void +InterfaceControl::call_on_change_callbacks() const +{ + for (const auto& on_change : m_on_change_callbacks) + on_change(); +} diff --git a/src/interface/control.hpp b/src/interface/control.hpp index 6c9a7f5129..a42f8e220a 100644 --- a/src/interface/control.hpp +++ b/src/interface/control.hpp @@ -53,11 +53,14 @@ class InterfaceControl : public Widget inline void set_rect(const Rectf& rect) { m_rect = rect; } inline Rectf get_rect() const { return m_rect; } +protected: + void call_on_change_callbacks() const; + public: /** Optional; a function that will be called each time the bound value * is modified. */ - std::function m_on_change; + std::vector> m_on_change_callbacks; /** Optional; the label associated with the control */ std::unique_ptr m_label; diff --git a/src/interface/control_button.cpp b/src/interface/control_button.cpp index 6acd889731..605042dc6c 100644 --- a/src/interface/control_button.cpp +++ b/src/interface/control_button.cpp @@ -60,8 +60,7 @@ ControlButton::on_mouse_button_up(const SDL_MouseButtonEvent& button) m_mouse_down = false; - if (m_on_change) - m_on_change(); + call_on_change_callbacks(); m_has_focus = true; @@ -90,8 +89,7 @@ ControlButton::on_key_up(const SDL_KeyboardEvent& key) return false; if (key.keysym.sym == SDLK_SPACE) { - if (m_on_change) - m_on_change(); + call_on_change_callbacks(); m_mouse_down = false; return true; } diff --git a/src/interface/control_checkbox.cpp b/src/interface/control_checkbox.cpp index 518c4bfdd3..4122e8144b 100644 --- a/src/interface/control_checkbox.cpp +++ b/src/interface/control_checkbox.cpp @@ -58,8 +58,7 @@ ControlCheckbox::on_mouse_button_up(const SDL_MouseButtonEvent& button) *m_value = !*m_value; - if (m_on_change) - m_on_change(); + call_on_change_callbacks(); m_has_focus = true; @@ -84,8 +83,6 @@ ControlCheckbox::on_key_up(const SDL_KeyboardEvent& key) *m_value = !*m_value; - if (m_on_change) - m_on_change(); - + call_on_change_callbacks(); return true; } diff --git a/src/interface/control_enum.hpp b/src/interface/control_enum.hpp index 62c9ebe0c7..d5857c0a69 100644 --- a/src/interface/control_enum.hpp +++ b/src/interface/control_enum.hpp @@ -184,8 +184,7 @@ ControlEnum::on_mouse_button_down(const SDL_MouseButtonEvent& button) if (--pos != -1) continue; *m_value = option.first; - if (m_on_change) - m_on_change(); + call_on_change_callbacks(); break; } @@ -255,9 +254,7 @@ ControlEnum::on_key_down(const SDL_KeyboardEvent& key) if (is_next && !m_options.empty()) *m_value = m_options.begin()->first; - if (m_on_change) - m_on_change(); - + call_on_change_callbacks(); return true; } else if (key.keysym.sym == SDLK_UP) { @@ -281,9 +278,7 @@ ControlEnum::on_key_down(const SDL_KeyboardEvent& key) if (is_last) *m_value = last_value; - if (m_on_change) - m_on_change(); - + call_on_change_callbacks(); return true; } diff --git a/src/interface/control_textbox.cpp b/src/interface/control_textbox.cpp index 93adebb95b..f30df74442 100644 --- a/src/interface/control_textbox.cpp +++ b/src/interface/control_textbox.cpp @@ -332,8 +332,8 @@ ControlTextbox::parse_value(bool call_on_change /* = true (see header)*/) if (m_string) *m_string = new_str; - if (call_on_change && m_on_change) - m_on_change(); + if (call_on_change) + call_on_change_callbacks(); } return true; diff --git a/src/interface/control_textbox_float.cpp b/src/interface/control_textbox_float.cpp index 93f75630ee..8e0d38b1b0 100644 --- a/src/interface/control_textbox_float.cpp +++ b/src/interface/control_textbox_float.cpp @@ -71,8 +71,8 @@ ControlTextboxFloat::parse_value(bool call_on_change /* = true (see header */) // Revert the value regardless. revert_value(); - if (call_on_change && m_on_change) - m_on_change(); + if (call_on_change) + call_on_change_callbacks(); } return true; diff --git a/src/interface/control_textbox_int.cpp b/src/interface/control_textbox_int.cpp index 721196cc8b..fda0aa9742 100644 --- a/src/interface/control_textbox_int.cpp +++ b/src/interface/control_textbox_int.cpp @@ -71,8 +71,8 @@ ControlTextboxInt::parse_value(bool call_on_change /* = true (see header */) // Revert the value regardless. revert_value(); - if (call_on_change && m_on_change) - m_on_change(); + if (call_on_change) + call_on_change_callbacks(); } return true; diff --git a/src/object/background.cpp b/src/object/background.cpp index 2545e6400c..bd13752b6d 100644 --- a/src/object/background.cpp +++ b/src/object/background.cpp @@ -279,8 +279,8 @@ Background::get_settings() { ObjectSettings result = GameObject::get_settings(); - result.add_float(_("X"), &m_pos.x, "x", 0.0f, OPTION_HIDDEN); - result.add_float(_("Y"), &m_pos.y, "y", 0.0f, OPTION_HIDDEN); + result.add_float(_("X"), &m_pos.x, "x", 0.0f, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_float(_("Y"), &m_pos.y, "y", 0.0f, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); result.add_bool(_("Fill"), &m_fill, "fill", false); result.add_int(_("Z-pos"), &m_layer, "z-pos", LAYER_BACKGROUND0); diff --git a/src/object/bicycle_platform.cpp b/src/object/bicycle_platform.cpp index 1ec458f856..22587300f9 100644 --- a/src/object/bicycle_platform.cpp +++ b/src/object/bicycle_platform.cpp @@ -198,8 +198,8 @@ BicyclePlatform::get_settings() { auto result = GameObject::get_settings(); - result.add_float(_("X"), &m_center.x, "x", 0.0f, OPTION_HIDDEN); - result.add_float(_("Y"), &m_center.y, "y", 0.0f, OPTION_HIDDEN); + result.add_float(_("X"), &m_center.x, "x", 0.0f, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_float(_("Y"), &m_center.y, "y", 0.0f, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); result.add_int(_("Platforms"), &m_platforms, "platforms", 2); result.add_float(_("Radius"), &m_radius, "radius", 128.0f); diff --git a/src/object/firefly.cpp b/src/object/firefly.cpp index 7c10ba1ebb..c9324e748f 100644 --- a/src/object/firefly.cpp +++ b/src/object/firefly.cpp @@ -141,7 +141,7 @@ ObjectSettings Firefly::get_settings() { ObjectSettings result = MovingSprite::get_settings(); - result.add_test_from_here(); + result.add_test_from_here(this); return result; } diff --git a/src/object/pneumatic_platform.cpp b/src/object/pneumatic_platform.cpp index 7fc0da773c..1c704555e4 100644 --- a/src/object/pneumatic_platform.cpp +++ b/src/object/pneumatic_platform.cpp @@ -157,8 +157,8 @@ PneumaticPlatform::get_settings() ObjectSettings result = GameObject::get_settings(); result.add_sprite(_("Sprite"), &m_sprite_name, "sprite", "images/objects/platforms/small.sprite"); - result.add_float(_("X"), &m_pos.x, "x", 0.0f, OPTION_HIDDEN); - result.add_float(_("Y"), &m_pos.y, "y", 0.0f, OPTION_HIDDEN); + result.add_float(_("X"), &m_pos.x, "x", 0.0f, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_float(_("Y"), &m_pos.y, "y", 0.0f, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); return result; } diff --git a/src/object/scripted_object.cpp b/src/object/scripted_object.cpp index 2d91111f15..35c5b2aec1 100644 --- a/src/object/scripted_object.cpp +++ b/src/object/scripted_object.cpp @@ -67,8 +67,8 @@ ScriptedObject::get_settings() ObjectSettings result = MovingSprite::get_settings(); - //result.add_float("width", &new_size.x, "width", OPTION_HIDDEN); - //result.add_float("height", &new_size.y, "height", OPTION_HIDDEN); + //result.add_float("width", &new_size.x, "width", OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + //result.add_float("height", &new_size.y, "height", OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); result.add_bool(_("Solid"), &solid, "solid", true); result.add_bool(_("Physics enabled"), &physic_enabled, "physic-enabled", true); result.add_bool(_("Visible"), &visible, "visible", true); diff --git a/src/object/spawnpoint.cpp b/src/object/spawnpoint.cpp index 18d2352def..aba891fb03 100644 --- a/src/object/spawnpoint.cpp +++ b/src/object/spawnpoint.cpp @@ -56,6 +56,6 @@ ObjectSettings SpawnPointMarker::get_settings() { ObjectSettings result = MovingObject::get_settings(); - result.add_test_from_here(); + result.add_test_from_here(this); return result; } diff --git a/src/supertux/moving_object.cpp b/src/supertux/moving_object.cpp index 9e1d38d253..c636c2b8e3 100644 --- a/src/supertux/moving_object.cpp +++ b/src/supertux/moving_object.cpp @@ -64,11 +64,11 @@ MovingObject::get_settings() if (has_variable_size()) { - result.add_float(_("Width"), &m_col.m_bbox.get_width(), "width", {}, OPTION_HIDDEN); - result.add_float(_("Height"), &m_col.m_bbox.get_height(), "height", {}, OPTION_HIDDEN); + result.add_float(_("Width"), &m_col.m_bbox.get_width(), "width", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_float(_("Height"), &m_col.m_bbox.get_height(), "height", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); } - result.add_float(_("X"), &m_col.m_bbox.get_left(), "x", {}, OPTION_HIDDEN); - result.add_float(_("Y"), &m_col.m_bbox.get_top(), "y", {}, OPTION_HIDDEN); + result.add_float(_("X"), &m_col.m_bbox.get_left(), "x", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_float(_("Y"), &m_col.m_bbox.get_top(), "y", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); return result; } diff --git a/src/worldmap/spawn_point.cpp b/src/worldmap/spawn_point.cpp index 8598b14501..3bbc2d1327 100644 --- a/src/worldmap/spawn_point.cpp +++ b/src/worldmap/spawn_point.cpp @@ -80,7 +80,7 @@ SpawnPointObject::get_settings() result.reorder({"auto-dir", "name", "x", "y"}); - result.add_test_from_here(); + result.add_test_from_here(this); return result; } diff --git a/src/worldmap/worldmap_object.cpp b/src/worldmap/worldmap_object.cpp index 57598ee463..446668a5b2 100644 --- a/src/worldmap/worldmap_object.cpp +++ b/src/worldmap/worldmap_object.cpp @@ -74,8 +74,8 @@ WorldMapObject::get_settings() result.remove("x"); result.remove("y"); - result.add_int(_("X"), &m_tile_x, "x", {}, OPTION_HIDDEN); - result.add_int(_("Y"), &m_tile_y, "y", {}, OPTION_HIDDEN); + result.add_int(_("X"), &m_tile_x, "x", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_int(_("Y"), &m_tile_y, "y", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); return result; } From 863d22c691bc2731a84e0faad2cc818de3c51af2 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:10:40 +0300 Subject: [PATCH 082/141] More `ObjectOption`s now support being interface controls --- src/editor/object_option.cpp | 78 ++++++++++++++++++++-------------- src/editor/object_option.hpp | 19 --------- src/editor/object_settings.cpp | 6 --- src/editor/object_settings.hpp | 3 -- src/editor/overlay_widget.cpp | 4 -- 5 files changed, 46 insertions(+), 64 deletions(-) diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 97a12ae4d4..b286681e1f 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -26,9 +26,14 @@ #include "editor/object_menu.hpp" #include "gui/item_stringselect.hpp" #include "gui/menu.hpp" +#include "gui/menu_color.hpp" +#include "gui/menu_filesystem.hpp" +#include "gui/menu_list.hpp" #include "gui/menu_manager.hpp" #include "gui/menu_object_select.hpp" +#include "gui/menu_paths.hpp" #include "gui/menu_script.hpp" +#include "gui/menu_string_array.hpp" #include "interface/control_button.hpp" #include "interface/control_checkbox.hpp" #include "interface/control_enum.hpp" @@ -699,7 +704,13 @@ ColorObjectOption::add_to_menu(Menu& menu) const std::unique_ptr ColorObjectOption::create_interface_control() const { - return nullptr; + auto button = std::make_unique(_("Mix...")); + button->m_on_change_callbacks.emplace_back([color = m_value_pointer]() + { + MenuManager::instance().push_menu(std::make_unique(color)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; } ObjectSelectObjectOption::ObjectSelectObjectOption(const std::string& text, std::vector>* pointer, @@ -741,8 +752,7 @@ ObjectSelectObjectOption::parse(const ReaderMapping& reader) void ObjectSelectObjectOption::save(Writer& writer) const -{MenuManager::instance().push_menu(std::make_unique(input, extensions, basedir, - path_relative_to_basedir, nullptr, item_processor)); +{ if (get_key().empty()) return; @@ -785,7 +795,13 @@ ObjectSelectObjectOption::add_to_menu(Menu& menu) const std::unique_ptr ObjectSelectObjectOption::create_interface_control() const { - return nullptr; + auto button = std::make_unique(_("Select...")); + button->m_on_change_callbacks.emplace_back( + [pointer = m_value_pointer, get_objects_param = m_get_objects_param, add_object_func = m_add_object_function]() { + MenuManager::instance().push_menu(std::make_unique(*pointer, get_objects_param, add_object_func)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; } TilesObjectOption::TilesState::TilesState() : @@ -976,7 +992,14 @@ PathRefObjectOption::add_to_menu(Menu& menu) const std::unique_ptr PathRefObjectOption::create_interface_control() const { - return nullptr; + auto button = std::make_unique(_("Change...")); + button->m_on_change_callbacks.emplace_back( + [target_ptr = m_value_pointer, path_ref = m_path_ref]() { + MenuManager::instance().push_menu(std::make_unique(*target_ptr, path_ref)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; + } SExpObjectOption::SExpObjectOption(const std::string& text, const std::string& key, sexp::Value& value, @@ -1148,31 +1171,12 @@ ParticleEditorOption::add_to_menu(Menu& menu) const std::unique_ptr ParticleEditorOption::create_interface_control() const { - return nullptr; -} - -ButtonOption::ButtonOption(const std::string& text, std::function callback) : - ObjectOption(text, "", 0), - m_callback(std::move(callback)) -{ -} - -std::string -ButtonOption::to_string() const -{ - return {}; -} - -void -ButtonOption::add_to_menu(Menu& menu) const -{ - menu.add_entry(get_text(), m_callback); -} - -std::unique_ptr -ButtonOption::create_interface_control() const -{ - return nullptr; + auto button = std::make_unique(_("Open")); + button->m_on_change_callbacks.emplace_back([]() { + Editor::current()->m_particle_editor_request = true; + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; } StringArrayOption::StringArrayOption(const std::string& text, const std::string& key, std::vector& items) : @@ -1201,7 +1205,12 @@ StringArrayOption::add_to_menu(Menu& menu) const std::unique_ptr StringArrayOption::create_interface_control() const { - return nullptr; + auto button = std::make_unique(_("Change...")); + button->m_on_change_callbacks.emplace_back([items_ptr = &m_items]() { + MenuManager::instance().push_menu(std::make_unique(*items_ptr)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; } ListOption::ListOption(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr) : @@ -1230,7 +1239,12 @@ ListOption::add_to_menu(Menu& menu) const std::unique_ptr ListOption::create_interface_control() const { - return nullptr; + auto button = std::make_unique(_("Change...")); + button->m_on_change_callbacks.emplace_back([items = m_items, value_ptr = m_value_pointer]() { + MenuManager::instance().push_menu(std::make_unique(items, value_ptr, nullptr)); + }); + button->set_rect(Rectf(0, 32, 20, 32)); + return button; } DirectionOption::DirectionOption(const std::string& text, Direction* value_ptr, diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index 52b76a2cb1..b69077cb11 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -535,25 +535,6 @@ class ParticleEditorOption final : public ObjectOption<> ParticleEditorOption& operator=(const ParticleEditorOption&) = delete; }; -class ButtonOption final : public ObjectOption<> -{ -public: - ButtonOption(const std::string& text, std::function callback); - - virtual void parse(const ReaderMapping& reader) override {} - virtual void save(Writer& writer) const override {} - virtual std::string to_string() const override; - virtual void add_to_menu(Menu& menu) const override; - virtual std::unique_ptr create_interface_control() const override; - -private: - std::function m_callback; - -private: - ButtonOption(const ButtonOption&) = delete; - ButtonOption& operator=(const ButtonOption&) = delete; -}; - class StringArrayOption final : public ObjectOption<> { public: diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index 8d6de9a132..a1771127b5 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -373,12 +373,6 @@ ObjectSettings::add_path_handle(const std::string& text, add_option(std::make_unique(text, handle, key, flags)); } -void -ObjectSettings::add_button(const std::string& text, const std::function& callback) -{ - add_option(std::make_unique(text, callback)); -} - void ObjectSettings::add_list(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr) { diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index 81188eb7fb..1be6b8efa6 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -159,9 +159,6 @@ class ObjectSettings final const std::string& key = {}, unsigned int flags = 0); void add_list(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr); - // VERY UNSTABLE - use with care ~ Semphris (author of that option) - void add_button(const std::string& text, const std::function& callback); - inline const std::vector>& get_options() const { return m_options; } /** Reorder the options in the given order, this is a hack to get diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 45d62742ce..cccb437d07 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -706,9 +706,7 @@ EditorOverlayWidget::update_properties_panel(GameObject* hovered_object) if (!hovered_object || !g_config->editor_show_properties_sidebar) return; - hovered_object->save_state(); ObjectSettings os = hovered_object->get_settings(); - for (const auto& option : os.get_options()) { if ((option->get_flags() & OPTION_HIDDEN) && !(option->get_flags() & OPTION_VISIBLE_PROPERTIES)) @@ -724,8 +722,6 @@ EditorOverlayWidget::update_properties_panel(GameObject* hovered_object) // TODO: Updating the object doesn't work every time. // Investigate why this is the case! hovered_object->after_editor_set(); - hovered_object->check_state(); - update_properties_panel(hovered_object); }); m_editor.add_control(option->get_text(), std::move(control), option->get_description()); From 4334174008159e06d91a7f8112607f0199f7a5e4 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:23:57 +0300 Subject: [PATCH 083/141] Do not propagate mouse events to editor or its widgets when hovering over sidebar --- src/editor/editor.cpp | 161 +++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 8b66bf5cdd..a63d84a2b3 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -321,20 +321,22 @@ Editor::draw(Compositor& compositor) { auto& context = compositor.make_context(); - if (m_levelloaded) { - for(const auto& widget : m_widgets) { - if (!g_config->editor_show_toolbar_widgets && - dynamic_cast(widget.get())) - { - continue; - } + if (m_levelloaded) + { + for(const auto& widget : m_widgets) + { + if (!g_config->editor_show_toolbar_widgets && + dynamic_cast(widget.get())) + { + continue; + } widget->draw(context); } m_overlay_widget->draw_tilemap_outer_shading(context); m_overlay_widget->draw_tilemap_border(context); - if (m_controls.size() != 0 && g_config->editor_show_properties_sidebar) + if (!m_controls.empty() && g_config->editor_show_properties_sidebar) { context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); @@ -1108,79 +1110,96 @@ Editor::event(const SDL_Event& ev) try { - if (ev.type == SDL_KEYDOWN) + if (ev.type == SDL_MOUSEMOTION) { - m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; - m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; - - if (m_ctrl_pressed) - m_scroll_speed = 16.0f; - else if (ev.key.keysym.mod & KMOD_RSHIFT) - m_scroll_speed = 96.0f; + m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y); - if (ev.key.keysym.sym == SDLK_F6) + // If properties sidebar controls are active and the mouse is hovering over the sidebar, + // do not propagate mouse motion to the editor or its widgets. + if (!m_controls.empty() && Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f).contains(m_mouse_pos)) + return; + } + else + { + // If properties sidebar controls are active and the mouse is hovering over the sidebar, + // do not propagate mouse events to the editor or its widgets. + if (!m_controls.empty() && + (ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEBUTTONUP || ev.type == SDL_MOUSEWHEEL) && + Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f).contains(m_mouse_pos)) { - Compositor::s_render_lighting = !Compositor::s_render_lighting; return; } - else if (m_ctrl_pressed) + + if (ev.type == SDL_KEYDOWN) { - switch (ev.key.keysym.sym) + m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; + m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; + + if (m_ctrl_pressed) + m_scroll_speed = 16.0f; + else if (ev.key.keysym.mod & KMOD_RSHIFT) + m_scroll_speed = 96.0f; + + if (ev.key.keysym.sym == SDLK_F6) { - case SDLK_t: - if (m_shift_pressed) - test_level(std::pair(get_sector()->get_name(), m_overlay_widget->get_sector_pos())); - else - test_level(std::nullopt); - break; - case SDLK_s: - save_level(); - break; - case SDLK_z: - undo(); - break; - case SDLK_y: - redo(); - break; - case SDLK_x: - toggle_tile_object_mode(); - break; - case SDLK_PLUS: // Zoom in - case SDLK_KP_PLUS: - m_new_scale = m_sector->get_camera().get_current_scale() + CAMERA_ZOOM_SENSITIVITY; - break; - case SDLK_MINUS: // Zoom out - case SDLK_KP_MINUS: - m_new_scale = m_sector->get_camera().get_current_scale() - CAMERA_ZOOM_SENSITIVITY; - break; - case SDLK_d: // Reset zoom - m_new_scale = 1.f; - break; + Compositor::s_render_lighting = !Compositor::s_render_lighting; + return; + } + else if (m_ctrl_pressed) + { + switch (ev.key.keysym.sym) + { + case SDLK_t: + if (m_shift_pressed) + test_level(std::pair(get_sector()->get_name(), m_overlay_widget->get_sector_pos())); + else + test_level(std::nullopt); + break; + case SDLK_s: + save_level(); + break; + case SDLK_z: + undo(); + break; + case SDLK_y: + redo(); + break; + case SDLK_x: + toggle_tile_object_mode(); + break; + case SDLK_PLUS: // Zoom in + case SDLK_KP_PLUS: + m_new_scale = m_sector->get_camera().get_current_scale() + CAMERA_ZOOM_SENSITIVITY; + break; + case SDLK_MINUS: // Zoom out + case SDLK_KP_MINUS: + m_new_scale = m_sector->get_camera().get_current_scale() - CAMERA_ZOOM_SENSITIVITY; + break; + case SDLK_d: // Reset zoom + m_new_scale = 1.f; + break; + } } } - } - else if (ev.type == SDL_KEYUP) - { - m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; - m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; + else if (ev.type == SDL_KEYUP) + { + m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; + m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; - if (!m_ctrl_pressed && !(ev.key.keysym.mod & KMOD_RSHIFT)) - m_scroll_speed = 32.0f; - } - else if (ev.type == SDL_MOUSEMOTION) - { - m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y); - } - else if (ev.type == SDL_MOUSEWHEEL && !m_toolbox_widget->has_mouse_focus() && !m_layers_widget->has_mouse_focus()) - { - // Scroll or zoom with mouse wheel, if the mouse is not over the toolbox. - // The toolbox does scrolling independently from the main area. - if (m_ctrl_pressed) - m_new_scale = m_sector->get_camera().get_current_scale() + static_cast(ev.wheel.y) * CAMERA_ZOOM_SENSITIVITY; - else if (m_shift_pressed) - scroll({ static_cast(ev.wheel.y * -40), static_cast(ev.wheel.x * -40) }); - else - scroll({ static_cast(ev.wheel.x * -40), static_cast(ev.wheel.y * -40) }); + if (!m_ctrl_pressed && !(ev.key.keysym.mod & KMOD_RSHIFT)) + m_scroll_speed = 32.0f; + } + else if (ev.type == SDL_MOUSEWHEEL && !m_toolbox_widget->has_mouse_focus() && !m_layers_widget->has_mouse_focus()) + { + // Scroll or zoom with mouse wheel, if the mouse is not over the toolbox. + // The toolbox does scrolling independently from the main area. + if (m_ctrl_pressed) + m_new_scale = m_sector->get_camera().get_current_scale() + static_cast(ev.wheel.y) * CAMERA_ZOOM_SENSITIVITY; + else if (m_shift_pressed) + scroll({ static_cast(ev.wheel.y * -40), static_cast(ev.wheel.x * -40) }); + else + scroll({ static_cast(ev.wheel.x * -40), static_cast(ev.wheel.y * -40) }); + } } BIND_SECTOR(*m_sector); From 0c81c784f59958745df9be6e69b53603a02d43e9 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:29:07 +0300 Subject: [PATCH 084/141] Clicking off from object now hides properties panel --- src/editor/overlay_widget.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index cccb437d07..27ef2cb443 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -619,6 +619,7 @@ EditorOverlayWidget::grab_object() if (!m_hovered_object->is_valid()) { m_hovered_object = nullptr; + update_properties_panel(nullptr); } else { @@ -648,6 +649,8 @@ EditorOverlayWidget::grab_object() { delete_markers(); } + + update_properties_panel(nullptr); } } @@ -724,11 +727,6 @@ EditorOverlayWidget::update_properties_panel(GameObject* hovered_object) hovered_object->after_editor_set(); }); m_editor.add_control(option->get_text(), std::move(control), option->get_description()); - - //auto textbox = std::make_unique(); - //textbox.get()->set_rect(Rectf(0, 32, 200, 32)); - //textbox.get()->put_text(option.get()->to_string()); - //m_editor.addControl(text, std::move(textbox), description); } } From 67f79d5e12186aa24c3293e8366b360b26d04b67 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:46:05 +0300 Subject: [PATCH 085/141] Editor: Do not save pointers to some toolbar button widgets They were not used anywhere. --- src/editor/editor.cpp | 23 +++++------------------ src/editor/editor.hpp | 3 --- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index a63d84a2b3..31f61d5eb1 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -129,9 +129,6 @@ Editor::Editor() : m_controls(), m_undo_widget(), m_redo_widget(), - m_grid_size_widget(), - m_play_widget(), - m_save_widget(), m_overlay_widget(), m_toolbox_widget(), m_layers_widget(), @@ -173,32 +170,23 @@ Editor::Editor() : else snap_grid_size--; }); - grid_size_widget->set_help_text(_("Change / Toggle grid size")); - m_grid_size_widget = grid_size_widget.get(); - m_widgets.insert(m_widgets.begin() + 2, std::move(grid_size_widget)); auto play_button = std::make_unique("images/engine/editor/play_button.png", Vector(96, 0), [this] { m_test_request = true; }); play_button->set_help_text(_("Test level")); - - m_play_widget = play_button.get(); - m_widgets.insert(m_widgets.begin() + 3, std::move(play_button)); auto save_button = std::make_unique("images/engine/editor/save.png", Vector(128, 0), [this] { - bool saved = save_level(); - auto notif = std::make_unique("save_level_notif", false, true); - notif->set_text(saved ? _("Level saved!") : _("Level failed to save.")); - MenuManager::instance().set_notification(std::move(notif)); - } + bool saved = save_level(); + auto notif = std::make_unique("save_level_notif", false, true); + notif->set_text(saved ? _("Level saved!") : _("Level failed to save.")); + MenuManager::instance().set_notification(std::move(notif)); + } ); save_button->set_help_text(_("Save level")); - - m_save_widget = save_button.get(); - m_widgets.insert(m_widgets.begin() + 4, std::move(save_button)); auto mode_button = std::make_unique("images/engine/editor/toggle_tile_object_mode.png", @@ -207,7 +195,6 @@ Editor::Editor() : } ); mode_button->set_help_text(_("Toggle between object and tile mode")); - m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); auto mouse_select_button = std::make_unique( diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 11d667cada..8b5ca0cb9f 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -252,9 +252,6 @@ class Editor final : public Screen, EditorToolbarButtonWidget* m_undo_widget; EditorToolbarButtonWidget* m_redo_widget; - EditorToolbarButtonWidget* m_grid_size_widget; - EditorToolbarButtonWidget* m_play_widget; - EditorToolbarButtonWidget* m_save_widget; EditorOverlayWidget* m_overlay_widget; EditorToolboxWidget* m_toolbox_widget; From 09486c1761e7adf090110a65a34ebf20ae2e660e Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Fri, 22 Aug 2025 19:18:23 +0200 Subject: [PATCH 086/141] Fix build on MacOS --- src/util/script_manager.cpp | 1 + src/util/script_manager.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/util/script_manager.cpp b/src/util/script_manager.cpp index ae86368cd4..02210390ed 100644 --- a/src/util/script_manager.cpp +++ b/src/util/script_manager.cpp @@ -23,6 +23,7 @@ #include ScriptManager::ScriptManager() : + m_scripts(), m_watcher() { diff --git a/src/util/script_manager.hpp b/src/util/script_manager.hpp index dbdf1487c1..8ad2b15a57 100644 --- a/src/util/script_manager.hpp +++ b/src/util/script_manager.hpp @@ -20,6 +20,7 @@ #include #include #include +#include class ScriptManager { From eaf748b394923d06f9a03635608cadb2f265ccf6 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 13:21:50 -0400 Subject: [PATCH 087/141] Fix tile selection being off (and other things) --- src/editor/overlay_widget.cpp | 3 +-- src/object/camera.cpp | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 27ef2cb443..9135b390e0 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -168,8 +168,7 @@ EditorOverlayWidget::drag_rect() const end_y = m_drag_start.y; } - return Rectf(start_x, start_y, end_x, end_y) - .moved(Vector(-200.0f, -32.0f)); + return Rectf(start_x, start_y, end_x, end_y); } void diff --git a/src/object/camera.cpp b/src/object/camera.cpp index ce75ff30bd..55adec3969 100644 --- a/src/object/camera.cpp +++ b/src/object/camera.cpp @@ -191,8 +191,6 @@ Camera::after_editor_set() init_path_pos(Vector(0,0)); } } - - m_translation -= Vector(200.f, 32.f); } void From de4ff6370e334d216df59591d99bb078faa49da6 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 22 Aug 2025 13:36:47 -0400 Subject: [PATCH 088/141] Fix editor hint text position --- src/editor/editor.cpp | 8 +++++++- src/editor/editor.hpp | 2 ++ src/editor/overlay_widget.cpp | 18 ++++++++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 31f61d5eb1..2333aac9e7 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -323,7 +323,7 @@ Editor::draw(Compositor& compositor) m_overlay_widget->draw_tilemap_outer_shading(context); m_overlay_widget->draw_tilemap_border(context); - if (!m_controls.empty() && g_config->editor_show_properties_sidebar) + if (get_properties_panel_visible()) { context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); @@ -1476,6 +1476,12 @@ Editor::pack_addon() *zip.Add_File(id + ".nfo") << ss.rdbuf(); } +bool +Editor::get_properties_panel_visible() +{ + return !m_controls.empty() && g_config->editor_show_properties_sidebar; +} + void Editor::add_control(const std::string& name, std::unique_ptr new_control, const std::string& description) { diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 8b5ca0cb9f..840f636157 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -166,6 +166,8 @@ class Editor final : public Screen, void update_properties_panel(GameObject* object) { m_overlay_widget->update_properties_panel(object); } + + bool get_properties_panel_visible(); void add_layer(GameObject* layer) { m_layers_widget->add_layer(layer); } diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 9135b390e0..067c681a19 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -1650,6 +1650,12 @@ EditorOverlayWidget::draw(DrawingContext& context) context.color().draw_text(Resources::normal_font, m_warning_text, Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::warning_color); } + Vector hint_pos(32, 16); + if (g_config->editor_show_toolbar_widgets || m_editor.get_properties_panel_visible()) + hint_pos.y += 32.f; + // TODO calculate width of rect + if (m_editor.get_properties_panel_visible()) + hint_pos.x += 200.f; if (g_config->editor_autotile_help) { if (m_autotile_mode) @@ -1659,29 +1665,29 @@ EditorOverlayWidget::draw(DrawingContext& context) { if (autotileset) { - context.color().draw_text(Resources::normal_font, fmt::format(fmt::runtime(_("Autotile erasing mode is on (\"{}\")")), autotileset->get_name()) + " " + get_autotileset_key_range(), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_active_color); + context.color().draw_text(Resources::normal_font, fmt::format(fmt::runtime(_("Autotile erasing mode is on (\"{}\")")), autotileset->get_name()) + " " + get_autotileset_key_range(), hint_pos, ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_active_color); } else { - context.color().draw_text(Resources::normal_font, _("Autotile erasing cannot be performed here"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_error_color); + context.color().draw_text(Resources::normal_font, _("Autotile erasing cannot be performed here"), hint_pos, ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_error_color); } } else if (autotileset) { - context.color().draw_text(Resources::normal_font, fmt::format(fmt::runtime(_("Autotile mode is on (\"{}\")")), autotileset->get_name()) + " " + get_autotileset_key_range(), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_active_color); + context.color().draw_text(Resources::normal_font, fmt::format(fmt::runtime(_("Autotile mode is on (\"{}\")")), autotileset->get_name()) + " " + get_autotileset_key_range(), hint_pos, ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_active_color); } else { - context.color().draw_text(Resources::normal_font, _("Selected tile isn't autotileable"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_error_color); + context.color().draw_text(Resources::normal_font, _("Selected tile isn't autotileable"), hint_pos, ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_error_color); } } else if (m_editor.get_tiles()->pos(0, 0) == 0) { - context.color().draw_text(Resources::normal_font, _("Hold Ctrl to enable autotile erasing") + " " + get_autotileset_key_range(), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_available_color); + context.color().draw_text(Resources::normal_font, _("Hold Ctrl to enable autotile erasing") + " " + get_autotileset_key_range(), hint_pos, ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_available_color); } else { - context.color().draw_text(Resources::normal_font, _("Hold Ctrl to enable autotile") + " " + get_autotileset_key_range(), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_available_color); + context.color().draw_text(Resources::normal_font, _("Hold Ctrl to enable autotile") + " " + get_autotileset_key_range(), hint_pos, ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_available_color); } } } From b29d373f03dd7db43911787f5537f9c8d19dc972 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:35:52 +0300 Subject: [PATCH 089/141] Support undo/redo with properties sidebar --- src/editor/editor.cpp | 71 +++++++++++++++++-- src/editor/editor.hpp | 18 ++--- src/editor/layers_widget.cpp | 10 ++- src/editor/object_menu.cpp | 2 - src/editor/object_option.cpp | 20 +++--- src/editor/overlay_widget.cpp | 47 ++---------- src/editor/overlay_widget.hpp | 1 - src/editor/particle_editor.cpp | 20 +++--- src/gui/menu_manager.cpp | 11 +++ src/interface/control.cpp | 8 +++ src/interface/control.hpp | 5 ++ src/interface/control_button.cpp | 4 +- src/interface/control_checkbox.cpp | 4 ++ src/interface/control_enum.hpp | 4 ++ src/interface/control_textbox.cpp | 2 +- src/supertux/menu/debug_menu.cpp | 4 -- .../menu/editor_level_select_menu.cpp | 7 -- .../menu/editor_levelset_select_menu.cpp | 4 +- src/supertux/menu/editor_menu.cpp | 6 -- src/supertux/menu/editor_objectgroup_menu.cpp | 5 -- src/supertux/menu/editor_save_as.cpp | 5 -- src/supertux/menu/editor_sectors_menu.cpp | 6 -- src/supertux/menu/editor_tilegroup_menu.cpp | 5 -- 23 files changed, 142 insertions(+), 127 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 2333aac9e7..ac1d329885 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -99,6 +99,23 @@ Editor::is_active() } } +void +Editor::may_deactivate() +{ + auto* self = Editor::current(); + if (self) + self->m_deactivate_request = true; +} + +void +Editor::may_reactivate() +{ + auto* self = Editor::current(); + if (self) + self->m_reactivate_request = true; +} + + Editor::Editor() : m_level(), m_world(), @@ -132,6 +149,7 @@ Editor::Editor() : m_overlay_widget(), m_toolbox_widget(), m_layers_widget(), + m_selected_object(), m_testing_disabled(false), m_enabled(false), m_bgr_surface(Surface::from_file("images/engine/menu/bg_editor.png")), @@ -424,6 +442,14 @@ Editor::update(float dt_sec, const Controller& controller) if (m_reactivate_request) { m_enabled = true; m_reactivate_request = false; + + // It's possible that the editor is being re-activated due to exiting a menu, + // possibly one related to an object option. + if (m_selected_object) + { + m_selected_object->after_editor_set(); + m_selected_object->check_state(); + } } if (m_save_request) { @@ -708,6 +734,7 @@ Editor::set_sector(Sector* sector) } m_layers_widget->refresh(); + select_object(nullptr); } void @@ -1477,7 +1504,7 @@ Editor::pack_addon() } bool -Editor::get_properties_panel_visible() +Editor::get_properties_panel_visible() const { return !m_controls.empty() && g_config->editor_show_properties_sidebar; } @@ -1493,7 +1520,7 @@ Editor::add_control(const std::string& name, std::unique_ptr n for (const auto& control : m_controls) height = std::max(height, control->get_rect().get_bottom() + 5.f); - auto control_rect = new_control.get()->get_rect(); + auto control_rect = new_control->get_rect(); Rectf target_rect; if (control_rect.get_width() == 0.f || control_rect.get_height() == 0.f) { @@ -1504,10 +1531,44 @@ Editor::add_control(const std::string& name, std::unique_ptr n target_rect = Rectf(control_rect.get_left(), height, control_rect.get_right(), height + control_rect.get_height()); } - new_control.get()->set_rect(target_rect); + new_control->set_rect(target_rect); auto dimensions = Rectf(3.f, height, 100.f, height + 20.f); - new_control.get()->m_label = std::make_unique(dimensions, std::move(name), std::move(description)); - //new_control.get()->m_on_change_callbacks.emplace_back([this](){ this->push_version(); }); + new_control->m_label = std::make_unique(dimensions, std::move(name), std::move(description)); m_controls.push_back(std::move(new_control)); } + +void +Editor::select_object(GameObject* object) +{ + m_controls.clear(); + + if (!object || !g_config->editor_show_properties_sidebar) + { + m_selected_object = nullptr; + return; + } + m_selected_object = object; + + ObjectSettings os = object->get_settings(); + for (const auto& option : os.get_options()) + { + if ((option->get_flags() & OPTION_HIDDEN) && !(option->get_flags() & OPTION_VISIBLE_PROPERTIES)) + continue; + + auto control = option->create_interface_control(); + if (!control) + continue; + + control->m_on_activate_callbacks.emplace_back([object]() { + object->save_state(); + }); + control->m_on_change_callbacks.emplace_back([object]() { + // TODO: Updating the object doesn't work every time. + // Investigate why this is the case! + object->after_editor_set(); + object->check_state(); + }); + add_control(option->get_text(), std::move(control), option->get_description()); + } +} diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 840f636157..5013746dd6 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -52,8 +52,12 @@ class Editor final : public Screen, { public: using exit_cb_t = std::function; + static bool is_active(); + static void may_deactivate(); + static void may_reactivate(); + private: static bool is_autosave_file(const std::string& filename) { return StringUtil::has_suffix(filename, "~"); @@ -163,12 +167,6 @@ class Editor final : public Screen, m_overlay_widget->edit_path(path, new_marked_object); } - void update_properties_panel(GameObject* object) { - m_overlay_widget->update_properties_panel(object); - } - - bool get_properties_panel_visible(); - void add_layer(GameObject* layer) { m_layers_widget->add_layer(layer); } inline TileMap* get_selected_tilemap() const { return m_layers_widget->get_selected_tilemap(); } @@ -179,8 +177,8 @@ class Editor final : public Screen, void queue_layers_refresh(); - void add_control(const std::string& name, std::unique_ptr new_control, const std::string& description = ""); - inline void clear_controls() { m_controls.clear(); } + bool get_properties_panel_visible() const; + void select_object(GameObject* object); void retoggle_undo_tracking(); void undo_stack_cleanup(); @@ -208,6 +206,8 @@ class Editor final : public Screen, void update_keyboard(const Controller& controller); void keep_camera_in_bounds(); + void add_control(const std::string& name, std::unique_ptr new_control, const std::string& description = ""); + protected: std::shared_ptr m_level; std::unique_ptr m_world; @@ -259,6 +259,8 @@ class Editor final : public Screen, EditorToolboxWidget* m_toolbox_widget; EditorLayersWidget* m_layers_widget; + GameObject* m_selected_object; + bool m_enabled; SurfacePtr m_bgr_surface; diff --git a/src/editor/layers_widget.cpp b/src/editor/layers_widget.cpp index acc6de471d..344fa18a7c 100644 --- a/src/editor/layers_widget.cpp +++ b/src/editor/layers_widget.cpp @@ -218,12 +218,12 @@ EditorLayersWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) if (tilemap) { set_selected_tilemap(tilemap); m_editor.edit_path(tilemap->get_path_gameobject(), tilemap); - m_editor.update_properties_panel(tilemap); + m_editor.select_object(tilemap); } else { auto cam = dynamic_cast(m_layer_icons[m_hovered_layer]->get_layer()); if (cam) { m_editor.edit_path(cam->get_path_gameobject(), cam); - m_editor.update_properties_panel(cam); + m_editor.select_object(cam); } } } @@ -236,10 +236,8 @@ EditorLayersWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) else if (button.button == SDL_BUTTON_RIGHT) { if (m_hovered_item == HoveredItem::LAYERS && m_hovered_layer < m_layer_icons.size()) { - auto om = std::make_unique(m_layer_icons[m_hovered_layer]->get_layer()); - m_editor.m_deactivate_request = true; - MenuManager::instance().push_menu(std::move(om)); - m_editor.update_properties_panel(m_layer_icons[m_hovered_layer]->get_layer()); + MenuManager::instance().push_menu(std::make_unique(m_layer_icons[m_hovered_layer]->get_layer())); + m_editor.select_object(m_layer_icons[m_hovered_layer]->get_layer()); return true; } else { return false; diff --git a/src/editor/object_menu.cpp b/src/editor/object_menu.cpp index 8b7b31d06d..7620c9a9cf 100644 --- a/src/editor/object_menu.cpp +++ b/src/editor/object_menu.cpp @@ -107,7 +107,6 @@ ObjectMenu::menu_action(MenuItem& item) case MNID_REMOVE: m_editor.delete_markers(); - m_editor.m_reactivate_request = true; m_object->remove_me(); MenuManager::instance().pop_menu(); break; @@ -147,7 +146,6 @@ ObjectMenu::on_back_action() if (!MenuManager::instance().previous_menu()) { - m_editor.m_reactivate_request = true; if (!dynamic_cast(m_object)) { m_editor.sort_layers(); } diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index b286681e1f..21cdb4f206 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -399,7 +399,7 @@ std::unique_ptr StringMultilineObjectOption::create_interface_control() const { auto button = std::make_unique(_("Edit...")); - button->m_on_change_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { + button->m_on_activate_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { MenuManager::instance().push_menu(std::make_unique(key, value_ptr)); }); button->set_rect(Rectf(0, 32, 20, 32)); @@ -587,7 +587,7 @@ std::unique_ptr ScriptObjectOption::create_interface_control() const { auto button = std::make_unique(_("Edit...")); - button->m_on_change_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { + button->m_on_activate_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { MenuManager::instance().push_menu(std::make_unique(key, value_ptr)); }); button->set_rect(Rectf(0, 32, 20, 32)); @@ -647,7 +647,7 @@ std::unique_ptr FileObjectOption::create_interface_control() const { auto button = std::make_unique(_("Browse...")); - button->m_on_change_callbacks.emplace_back( + button->m_on_activate_callbacks.emplace_back( [input = m_value_pointer, extensions = m_filter, basedir = m_basedir, path_relative_to_basedir = m_path_relative_to_basedir]() { MenuManager::instance().push_menu(std::make_unique(input, extensions, basedir, path_relative_to_basedir)); @@ -705,7 +705,7 @@ std::unique_ptr ColorObjectOption::create_interface_control() const { auto button = std::make_unique(_("Mix...")); - button->m_on_change_callbacks.emplace_back([color = m_value_pointer]() + button->m_on_activate_callbacks.emplace_back([color = m_value_pointer]() { MenuManager::instance().push_menu(std::make_unique(color)); }); @@ -796,7 +796,7 @@ std::unique_ptr ObjectSelectObjectOption::create_interface_control() const { auto button = std::make_unique(_("Select...")); - button->m_on_change_callbacks.emplace_back( + button->m_on_activate_callbacks.emplace_back( [pointer = m_value_pointer, get_objects_param = m_get_objects_param, add_object_func = m_add_object_function]() { MenuManager::instance().push_menu(std::make_unique(*pointer, get_objects_param, add_object_func)); }); @@ -993,7 +993,7 @@ std::unique_ptr PathRefObjectOption::create_interface_control() const { auto button = std::make_unique(_("Change...")); - button->m_on_change_callbacks.emplace_back( + button->m_on_activate_callbacks.emplace_back( [target_ptr = m_value_pointer, path_ref = m_path_ref]() { MenuManager::instance().push_menu(std::make_unique(*target_ptr, path_ref)); }); @@ -1140,7 +1140,7 @@ TestFromHereOption::create_interface_control() const { auto button = std::make_unique(_("Test")); button->set_rect(Rectf(0, 32, 200, 32)); - button->m_on_change_callbacks.emplace_back([object_ptr = m_value_pointer]() { + button->m_on_activate_callbacks.emplace_back([object_ptr = m_value_pointer]() { Editor& editor = *Editor::current(); // TODO: Pressing the return key from within a game session automatically // triggers this button again if it's previously been pushed. This needs @@ -1172,7 +1172,7 @@ std::unique_ptr ParticleEditorOption::create_interface_control() const { auto button = std::make_unique(_("Open")); - button->m_on_change_callbacks.emplace_back([]() { + button->m_on_activate_callbacks.emplace_back([]() { Editor::current()->m_particle_editor_request = true; }); button->set_rect(Rectf(0, 32, 20, 32)); @@ -1206,7 +1206,7 @@ std::unique_ptr StringArrayOption::create_interface_control() const { auto button = std::make_unique(_("Change...")); - button->m_on_change_callbacks.emplace_back([items_ptr = &m_items]() { + button->m_on_activate_callbacks.emplace_back([items_ptr = &m_items]() { MenuManager::instance().push_menu(std::make_unique(*items_ptr)); }); button->set_rect(Rectf(0, 32, 20, 32)); @@ -1240,7 +1240,7 @@ std::unique_ptr ListOption::create_interface_control() const { auto button = std::make_unique(_("Change...")); - button->m_on_change_callbacks.emplace_back([items = m_items, value_ptr = m_value_pointer]() { + button->m_on_activate_callbacks.emplace_back([items = m_items, value_ptr = m_value_pointer]() { MenuManager::instance().push_menu(std::make_unique(items, value_ptr, nullptr)); }); button->set_rect(Rectf(0, 32, 20, 32)); diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 067c681a19..fe6cd1da44 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -618,7 +618,7 @@ EditorOverlayWidget::grab_object() if (!m_hovered_object->is_valid()) { m_hovered_object = nullptr; - update_properties_panel(nullptr); + m_editor.select_object(nullptr); } else { @@ -649,7 +649,7 @@ EditorOverlayWidget::grab_object() delete_markers(); } - update_properties_panel(nullptr); + m_editor.select_object(nullptr); } } @@ -695,38 +695,7 @@ EditorOverlayWidget::clone_object() void EditorOverlayWidget::show_object_menu(GameObject& object) { - auto menu = std::make_unique(&object); - m_editor.m_deactivate_request = true; - MenuManager::instance().push_menu(std::move(menu)); -} - -void -EditorOverlayWidget::update_properties_panel(GameObject* hovered_object) -{ - m_editor.clear_controls(); - - if (!hovered_object || !g_config->editor_show_properties_sidebar) - return; - - ObjectSettings os = hovered_object->get_settings(); - for (const auto& option : os.get_options()) - { - if ((option->get_flags() & OPTION_HIDDEN) && !(option->get_flags() & OPTION_VISIBLE_PROPERTIES)) - continue; - - auto control = option->create_interface_control(); - if (!control) - continue; - - control->m_on_change_callbacks.emplace_back([hovered_object, this]() { - if (!hovered_object) - return; - // TODO: Updating the object doesn't work every time. - // Investigate why this is the case! - hovered_object->after_editor_set(); - }); - m_editor.add_control(option->get_text(), std::move(control), option->get_description()); - } + MenuManager::instance().push_menu(std::make_unique(&object)); } void @@ -771,7 +740,7 @@ EditorOverlayWidget::move_object() void EditorOverlayWidget::rubber_object() { - update_properties_panel(nullptr); + m_editor.select_object(nullptr); if (!m_edited_path) { delete_markers(); } @@ -920,11 +889,9 @@ EditorOverlayWidget::process_left_click() break; case EditorTilebox::InputType::NONE: - case EditorTilebox::InputType::OBJECT: - if (m_hovered_object.get() != nullptr) - { - update_properties_panel(m_hovered_object.get()); - } + case EditorTilebox::InputType::OBJECT: + if (m_hovered_object) + m_editor.select_object(m_hovered_object.get()); switch (m_editor.get_tileselect_move_mode()) { diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index 7a23fd59da..d5bde5f0f5 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -65,7 +65,6 @@ class EditorOverlayWidget final : public Widget void update_pos(); void update_autotileset(); - void update_properties_panel(GameObject* object); void delete_markers(); void update_node_iterators(); void on_level_change(); diff --git a/src/editor/particle_editor.cpp b/src/editor/particle_editor.cpp index b317a4ba8f..3362c3c592 100644 --- a/src/editor/particle_editor.cpp +++ b/src/editor/particle_editor.cpp @@ -112,7 +112,7 @@ ParticleEditor::reset_main_ui() // TODO: Use the addButton() command // Texture button start auto texture_btn = std::make_unique(_("Change texture... ->")); - texture_btn.get()->m_on_change_callbacks.emplace_back([this](){ + texture_btn.get()->m_on_activate_callbacks.emplace_back([this](){ m_in_texture_tab = true; }); float tmp_height = 0.f; @@ -227,7 +227,7 @@ ParticleEditor::reset_main_ui() // TODO: add some ParticleEditor::addButton() function so that I don't have to put all that in here auto clear_btn = std::make_unique(_("Clear")); - clear_btn.get()->m_on_change_callbacks.emplace_back([this](){ m_particles->clear(); }); + clear_btn.get()->m_on_activate_callbacks.emplace_back([this](){ m_particles->clear(); }); float height = 0.f; for (const auto& control : m_controls) { height = std::max(height, control->get_rect().get_bottom() + 5.f); @@ -243,7 +243,7 @@ ParticleEditor::reset_texture_ui() m_texture_rebinds.clear(); auto return_btn = std::make_unique(_("<- General settings")); - return_btn.get()->m_on_change_callbacks.emplace_back([this](){ + return_btn.get()->m_on_activate_callbacks.emplace_back([this](){ m_in_texture_tab = false; }); return_btn.get()->set_rect(Rectf(25.f, 0.f, 325.f, 20.f)); @@ -370,7 +370,7 @@ ParticleEditor::reset_texture_ui() // Texture button start auto chg_texture_btn = std::make_unique(_("Change texture...")); - chg_texture_btn.get()->m_on_change_callbacks.emplace_back([this](){ + chg_texture_btn.get()->m_on_activate_callbacks.emplace_back([this](){ const std::vector& filter = {".jpg", ".png", ".surface"}; MenuManager::instance().push_menu(std::make_unique( nullptr, @@ -389,7 +389,7 @@ ParticleEditor::reset_texture_ui() // Texture button end auto prev_btn = std::make_unique("<"); - prev_btn.get()->m_on_change_callbacks.emplace_back([this](){ + prev_btn.get()->m_on_activate_callbacks.emplace_back([this](){ m_texture_current--; if (m_texture_current < 0) m_texture_current = 0; for (const auto& refresh : m_texture_rebinds) @@ -399,7 +399,7 @@ ParticleEditor::reset_texture_ui() m_controls_textures.push_back(std::move(prev_btn)); auto del_btn = std::make_unique("-"); - del_btn.get()->m_on_change_callbacks.emplace_back([this](){ + del_btn.get()->m_on_activate_callbacks.emplace_back([this](){ if (m_particles->m_textures.size() < 1) return; m_particles->m_textures.erase(m_particles->m_textures.begin() + m_texture_current); @@ -414,7 +414,7 @@ ParticleEditor::reset_texture_ui() m_controls_textures.push_back(std::move(del_btn)); auto add_btn = std::make_unique("+"); - add_btn.get()->m_on_change_callbacks.emplace_back([this](){ + add_btn.get()->m_on_activate_callbacks.emplace_back([this](){ m_particles->m_textures.push_back(CustomParticleSystem::SpriteProperties()); m_texture_current = static_cast(m_particles->m_textures.size()) - 1; for (const auto& refresh : m_texture_rebinds) @@ -426,7 +426,7 @@ ParticleEditor::reset_texture_ui() m_controls_textures.push_back(std::move(add_btn)); auto next_btn = std::make_unique(">"); - next_btn.get()->m_on_change_callbacks.emplace_back([this](){ + next_btn.get()->m_on_activate_callbacks.emplace_back([this](){ m_texture_current++; if (m_texture_current > static_cast(m_particles->m_textures.size()) - 1) m_texture_current = static_cast(m_particles->m_textures.size()) - 1; @@ -734,9 +734,7 @@ ParticleEditor::quit_editor() auto quit = [] () { ScreenManager::current()->pop_screen(); - if (Editor::current()) { - Editor::current()->m_reactivate_request = true; - } + Editor::may_reactivate(); }; check_unsaved_changes([quit] { diff --git a/src/gui/menu_manager.cpp b/src/gui/menu_manager.cpp index 7475ec7cd0..e86f5ed4c9 100644 --- a/src/gui/menu_manager.cpp +++ b/src/gui/menu_manager.cpp @@ -18,6 +18,7 @@ #include "gui/menu_manager.hpp" #include "control/input_manager.hpp" +#include "editor/editor.hpp" #include "gui/dialog.hpp" #include "gui/menu.hpp" #include "gui/mousecursor.hpp" @@ -229,12 +230,16 @@ MenuManager::set_menu(std::unique_ptr menu, bool skip_transition) transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(), menu.get()); m_menu_stack.clear(); m_menu_stack.push_back(std::move(menu)); + + Editor::may_deactivate(); } else { if (!skip_transition) transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(), nullptr); m_menu_stack.clear(); + + Editor::may_reactivate(); } // just to be sure... @@ -254,6 +259,8 @@ MenuManager::push_menu(std::unique_ptr menu, bool skip_transition) if (!skip_transition) transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(), menu.get()); m_menu_stack.push_back(std::move(menu)); + + Editor::may_deactivate(); } void @@ -269,6 +276,8 @@ MenuManager::pop_menu(bool skip_transition) transition(m_menu_stack.back().get(), m_menu_stack.size() >= 2 ? m_menu_stack[m_menu_stack.size() - 2].get() : nullptr); m_menu_stack.pop_back(); + + Editor::may_reactivate(); } void @@ -277,6 +286,8 @@ MenuManager::clear_menu_stack(bool skip_transition) if (!skip_transition) transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(), nullptr); m_menu_stack.clear(); + + Editor::may_reactivate(); } diff --git a/src/interface/control.cpp b/src/interface/control.cpp index 94b4b2c399..6627acf255 100644 --- a/src/interface/control.cpp +++ b/src/interface/control.cpp @@ -17,6 +17,7 @@ #include "interface/control.hpp" InterfaceControl::InterfaceControl() : + m_on_activate_callbacks(), m_on_change_callbacks(), m_label(), m_has_focus(), @@ -25,6 +26,13 @@ InterfaceControl::InterfaceControl() : { } +void +InterfaceControl::call_on_activate_callbacks() const +{ + for (const auto& on_activate : m_on_activate_callbacks) + on_activate(); +} + void InterfaceControl::call_on_change_callbacks() const { diff --git a/src/interface/control.hpp b/src/interface/control.hpp index a42f8e220a..4c6614b465 100644 --- a/src/interface/control.hpp +++ b/src/interface/control.hpp @@ -54,9 +54,14 @@ class InterfaceControl : public Widget inline Rectf get_rect() const { return m_rect; } protected: + void call_on_activate_callbacks() const; void call_on_change_callbacks() const; public: + /** Optional; a function that will be called each time the control is activated. + */ + std::vector> m_on_activate_callbacks; + /** Optional; a function that will be called each time the bound value * is modified. */ diff --git a/src/interface/control_button.cpp b/src/interface/control_button.cpp index 605042dc6c..f627a736fc 100644 --- a/src/interface/control_button.cpp +++ b/src/interface/control_button.cpp @@ -60,7 +60,7 @@ ControlButton::on_mouse_button_up(const SDL_MouseButtonEvent& button) m_mouse_down = false; - call_on_change_callbacks(); + call_on_activate_callbacks(); m_has_focus = true; @@ -89,7 +89,7 @@ ControlButton::on_key_up(const SDL_KeyboardEvent& key) return false; if (key.keysym.sym == SDLK_SPACE) { - call_on_change_callbacks(); + call_on_activate_callbacks(); m_mouse_down = false; return true; } diff --git a/src/interface/control_checkbox.cpp b/src/interface/control_checkbox.cpp index 4122e8144b..6434709749 100644 --- a/src/interface/control_checkbox.cpp +++ b/src/interface/control_checkbox.cpp @@ -56,6 +56,8 @@ ControlCheckbox::on_mouse_button_up(const SDL_MouseButtonEvent& button) if (!m_rect.contains(mouse_pos)) return false; + call_on_activate_callbacks(); + *m_value = !*m_value; call_on_change_callbacks(); @@ -81,6 +83,8 @@ ControlCheckbox::on_key_up(const SDL_KeyboardEvent& key) if (key.keysym.sym != SDLK_SPACE || !m_has_focus) return false; + call_on_activate_callbacks(); + *m_value = !*m_value; call_on_change_callbacks(); diff --git a/src/interface/control_enum.hpp b/src/interface/control_enum.hpp index d5857c0a69..50010f20c1 100644 --- a/src/interface/control_enum.hpp +++ b/src/interface/control_enum.hpp @@ -156,6 +156,8 @@ ControlEnum::on_mouse_button_up(const SDL_MouseButtonEvent& button) if (m_rect.contains(mouse_pos)) { m_open_list = !m_open_list; m_has_focus = true; + if (m_open_list) + call_on_activate_callbacks(); return true; } else if (get_list_rect().contains(mouse_pos) && m_open_list) { return true; @@ -224,6 +226,8 @@ ControlEnum::on_key_up(const SDL_KeyboardEvent& key) || key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_RETURN2) && m_has_focus) { m_open_list = !m_open_list; + if (m_open_list) + call_on_activate_callbacks(); return true; } else { return false; diff --git a/src/interface/control_textbox.cpp b/src/interface/control_textbox.cpp index f30df74442..89471ad0f3 100644 --- a/src/interface/control_textbox.cpp +++ b/src/interface/control_textbox.cpp @@ -134,6 +134,7 @@ ControlTextbox::on_mouse_button_down(const SDL_MouseButtonEvent& button) m_caret_pos = get_text_position(mouse_pos); m_secondary_caret_pos = m_caret_pos; m_mouse_pressed = true; + call_on_activate_callbacks(); return true; } else { if (m_has_focus) { @@ -171,7 +172,6 @@ ControlTextbox::on_mouse_motion(const SDL_MouseMotionEvent& motion) bool ControlTextbox::on_key_up(const SDL_KeyboardEvent& key) { - if (m_has_focus) { if (key.keysym.sym == SDLK_LSHIFT || key.keysym.sym == SDLK_RSHIFT) diff --git a/src/supertux/menu/debug_menu.cpp b/src/supertux/menu/debug_menu.cpp index f6b07edbc1..55751da614 100644 --- a/src/supertux/menu/debug_menu.cpp +++ b/src/supertux/menu/debug_menu.cpp @@ -87,10 +87,6 @@ DebugMenu::DebugMenu() : DebugMenu::~DebugMenu() { - auto editor = Editor::current(); - - if (editor == nullptr) return; - editor->m_reactivate_request = true; } void diff --git a/src/supertux/menu/editor_level_select_menu.cpp b/src/supertux/menu/editor_level_select_menu.cpp index 2df5765658..75daed2707 100644 --- a/src/supertux/menu/editor_level_select_menu.cpp +++ b/src/supertux/menu/editor_level_select_menu.cpp @@ -57,8 +57,6 @@ EditorLevelSelectMenu::reload_menu() void EditorLevelSelectMenu::initialize() { - Editor::current()->m_deactivate_request = true; - World* world = get_world(); auto basedir = world->get_basedir(); m_levelset = std::unique_ptr(new Levelset(basedir, /* recursively = */ true)); @@ -100,11 +98,6 @@ EditorLevelSelectMenu::initialize() EditorLevelSelectMenu::~EditorLevelSelectMenu() { - auto editor = Editor::current(); - if (editor == nullptr) { - return; - } - editor->m_reactivate_request = true; } World* diff --git a/src/supertux/menu/editor_levelset_select_menu.cpp b/src/supertux/menu/editor_levelset_select_menu.cpp index ad2c89c02e..27cdecdd81 100644 --- a/src/supertux/menu/editor_levelset_select_menu.cpp +++ b/src/supertux/menu/editor_levelset_select_menu.cpp @@ -50,14 +50,12 @@ EditorLevelsetSelectMenu::~EditorLevelsetSelectMenu() } if (!editor->is_level_loaded() && !editor->m_reload_request) { editor->m_quit_request = true; - } else { - editor->m_reactivate_request = true; } } + void EditorLevelsetSelectMenu::initialize() { - Editor::current()->m_deactivate_request = true; m_contrib_worlds.clear(); // Generating contrib levels list by making use of Level Subset diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index 78897b623f..499aed431f 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -126,12 +126,6 @@ EditorMenu::refresh() EditorMenu::~EditorMenu() { - auto editor = Editor::current(); - - if (editor == nullptr) - return; - - editor->m_reactivate_request = true; } void diff --git a/src/supertux/menu/editor_objectgroup_menu.cpp b/src/supertux/menu/editor_objectgroup_menu.cpp index 6380f2c308..8dd440aa6c 100644 --- a/src/supertux/menu/editor_objectgroup_menu.cpp +++ b/src/supertux/menu/editor_objectgroup_menu.cpp @@ -45,11 +45,6 @@ EditorObjectgroupMenu::EditorObjectgroupMenu() EditorObjectgroupMenu::~EditorObjectgroupMenu() { - auto editor = Editor::current(); - if (editor == nullptr) { - return; - } - editor->m_reactivate_request = true; } void diff --git a/src/supertux/menu/editor_save_as.cpp b/src/supertux/menu/editor_save_as.cpp index c53a6a6c47..1894135a80 100644 --- a/src/supertux/menu/editor_save_as.cpp +++ b/src/supertux/menu/editor_save_as.cpp @@ -44,11 +44,6 @@ EditorSaveAs::EditorSaveAs(bool do_switch_file) : EditorSaveAs::~EditorSaveAs() { - auto editor = Editor::current(); - if (editor == nullptr) { - return; - } - editor->m_reactivate_request = true; } void diff --git a/src/supertux/menu/editor_sectors_menu.cpp b/src/supertux/menu/editor_sectors_menu.cpp index 46c0b65c6c..22e33ee871 100644 --- a/src/supertux/menu/editor_sectors_menu.cpp +++ b/src/supertux/menu/editor_sectors_menu.cpp @@ -47,11 +47,6 @@ EditorSectorsMenu::EditorSectorsMenu() EditorSectorsMenu::~EditorSectorsMenu() { - auto editor = Editor::current(); - if (editor == nullptr) { - return; - } - editor->m_reactivate_request = true; } void @@ -78,7 +73,6 @@ EditorSectorsMenu::create_sector() level->add_sector(std::move(new_sector)); Editor::current()->load_sector(sector_name); MenuManager::instance().clear_menu_stack(); - Editor::current()->m_reactivate_request = true; } void diff --git a/src/supertux/menu/editor_tilegroup_menu.cpp b/src/supertux/menu/editor_tilegroup_menu.cpp index 63291960d2..1616b243ef 100644 --- a/src/supertux/menu/editor_tilegroup_menu.cpp +++ b/src/supertux/menu/editor_tilegroup_menu.cpp @@ -39,11 +39,6 @@ EditorTilegroupMenu::EditorTilegroupMenu() EditorTilegroupMenu::~EditorTilegroupMenu() { - auto editor = Editor::current(); - if (editor == nullptr) { - return; - } - editor->m_reactivate_request = true; } void From 08961d176aba672b9e5034495a31a187a40451bf Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Sat, 23 Aug 2025 01:04:13 +0300 Subject: [PATCH 090/141] Editor: Add indicator around selected object, use TypedUID to prevent dangling pointer segfaults --- src/editor/editor.cpp | 78 +++++++++++++++++++++++++++++++++---------- src/editor/editor.hpp | 2 +- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index ac1d329885..d49dbbf6ce 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -338,22 +338,22 @@ Editor::draw(Compositor& compositor) widget->draw(context); } - m_overlay_widget->draw_tilemap_outer_shading(context); - m_overlay_widget->draw_tilemap_border(context); - - if (get_properties_panel_visible()) - { - context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), - Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); + m_overlay_widget->draw_tilemap_outer_shading(context); + m_overlay_widget->draw_tilemap_border(context); - context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), - Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); + if (get_properties_panel_visible()) + { + context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), + Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); - for(const auto& control : m_controls) - { - control->draw(context); - } - } + context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), + Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); + + for(const auto& control : m_controls) + { + control->draw(context); + } + } // If camera scale must be changed, change it here. if (m_new_scale != 0.f) @@ -381,8 +381,49 @@ Editor::draw(Compositor& compositor) // Avoid drawing the sector if we're about to test it, as there is a dangling pointer // issue with the PlayerStatus. if (!m_leveltested) + { m_sector->draw(context); + // If an object is selected, draw an indicator around it. + const GameObject* selected_object = m_selected_object.get(); + if (selected_object) + { + const MovingObject* moving_selected_obj = dynamic_cast(selected_object); + if (moving_selected_obj) + { + context.push_transform(); + const Camera& camera = m_sector->get_camera(); + context.set_translation(camera.get_translation()); + context.scale(camera.get_current_scale()); + + const Rectf& bbox = moving_selected_obj->get_bbox(); + context.color().draw_line(bbox.p1() - Vector(10.f, 10.f), bbox.p1() - Vector(0.f, 10.f), Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(bbox.p1() - Vector(10.f, 10.f), bbox.p1() - Vector(10.f, 0.f), Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(bbox.p2() + Vector(10.f, 10.f), bbox.p2() + Vector(0.f, 10.f), Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(bbox.p2() + Vector(10.f, 10.f), bbox.p2() + Vector(10.f, 0.f), Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(Vector(bbox.get_right() + 10.f, bbox.get_top() - 10.f), + Vector(bbox.get_right() + 10.f, bbox.get_top()), + Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(Vector(bbox.get_right() + 10.f, bbox.get_top() - 10.f), + Vector(bbox.get_right(), bbox.get_top() - 10.f), + Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(Vector(bbox.get_left() - 10.f, bbox.get_bottom() + 10.f), + Vector(bbox.get_left() - 10.f, bbox.get_bottom()), + Color::WHITE, LAYER_GUI + 1); + context.color().draw_line(Vector(bbox.get_left() - 10.f, bbox.get_bottom() + 10.f), + Vector(bbox.get_left(), bbox.get_bottom() + 10.f), + Color::WHITE, LAYER_GUI + 1); + + context.pop_transform(); + } + } + else + { + m_selected_object = 0; + m_controls.clear(); + } + } + context.color().draw_filled_rect(context.get_rect(), Color(0.0f, 0.0f, 0.0f), 0.0f, std::numeric_limits::min()); @@ -445,10 +486,11 @@ Editor::update(float dt_sec, const Controller& controller) // It's possible that the editor is being re-activated due to exiting a menu, // possibly one related to an object option. - if (m_selected_object) + GameObject* selected_object = m_selected_object.get(); + if (selected_object) { - m_selected_object->after_editor_set(); - m_selected_object->check_state(); + selected_object->after_editor_set(); + selected_object->check_state(); } } @@ -1545,7 +1587,7 @@ Editor::select_object(GameObject* object) if (!object || !g_config->editor_show_properties_sidebar) { - m_selected_object = nullptr; + m_selected_object = 0; return; } m_selected_object = object; diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 5013746dd6..dfd9ae3482 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -259,7 +259,7 @@ class Editor final : public Screen, EditorToolboxWidget* m_toolbox_widget; EditorLayersWidget* m_layers_widget; - GameObject* m_selected_object; + TypedUID m_selected_object; bool m_enabled; SurfacePtr m_bgr_surface; From 9a20651d369a43ec5e50379410ad04b86a57cb8b Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Sat, 23 Aug 2025 01:17:32 +0300 Subject: [PATCH 091/141] Use the game's regular "small" font for interface controls for consistency --- src/interface/control_button.cpp | 6 +++--- src/interface/control_checkbox.cpp | 6 +++--- src/interface/control_enum.hpp | 12 ++++++------ src/interface/control_textbox.cpp | 18 +++++++++--------- src/interface/label.cpp | 8 ++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/interface/control_button.cpp b/src/interface/control_button.cpp index f627a736fc..88a5ee6336 100644 --- a/src/interface/control_button.cpp +++ b/src/interface/control_button.cpp @@ -37,13 +37,13 @@ ControlButton::draw(DrawingContext& context) (m_has_focus ? Color(0.75f, 0.75f, 0.7f, 1.f) : Color(0.5f, 0.5f, 0.5f, 1.f)), LAYER_GUI); - context.color().draw_text(Resources::control_font, + context.color().draw_text(Resources::small_font, m_btn_label, Vector((m_rect.get_left() + m_rect.get_right()) / 2, - (m_rect.get_top() + m_rect.get_bottom()) / 2 - Resources::control_font->get_height() / 2), + (m_rect.get_top() + m_rect.get_bottom()) / 2 - Resources::small_font->get_height() / 2), FontAlignment::ALIGN_CENTER, LAYER_GUI, - Color::BLACK); + Color::WHITE); } bool diff --git a/src/interface/control_checkbox.cpp b/src/interface/control_checkbox.cpp index 6434709749..fa9c1ff4c1 100644 --- a/src/interface/control_checkbox.cpp +++ b/src/interface/control_checkbox.cpp @@ -35,13 +35,13 @@ ControlCheckbox::draw(DrawingContext& context) m_has_focus ? Color(0.75f, 0.75f, 0.7f, 1.f) : Color(0.5f, 0.5f, 0.5f, 1.f), LAYER_GUI); if (*m_value) { - context.color().draw_text(Resources::control_font, + context.color().draw_text(Resources::small_font, "X", Vector((m_rect.get_left() + m_rect.get_right()) / 2 + 1.f, - (m_rect.get_top() + m_rect.get_bottom()) / 2 - Resources::control_font->get_height() / 2), + (m_rect.get_top() + m_rect.get_bottom()) / 2 - Resources::small_font->get_height() / 2), FontAlignment::ALIGN_CENTER, LAYER_GUI, - Color::BLACK); + Color::WHITE); } } diff --git a/src/interface/control_enum.hpp b/src/interface/control_enum.hpp index 50010f20c1..733760ee03 100644 --- a/src/interface/control_enum.hpp +++ b/src/interface/control_enum.hpp @@ -107,14 +107,14 @@ ControlEnum::draw(DrawingContext& context) label = ""; } - context.color().draw_text(Resources::control_font, + context.color().draw_text(Resources::small_font, label, Vector(m_rect.get_left() + 5.f, (m_rect.get_top() + m_rect.get_bottom()) / 2 - - Resources::control_font->get_height() / 2), + Resources::small_font->get_height() / 2), FontAlignment::ALIGN_LEFT, LAYER_GUI + 1, - Color::BLACK); + Color::WHITE); int i = 0; if (m_open_list) { for (const auto& option : m_options) { @@ -132,15 +132,15 @@ ControlEnum::draw(DrawingContext& context) std::string label2 = option.second; - context.color().draw_text(Resources::control_font, + context.color().draw_text(Resources::small_font, label2, Vector(m_rect.get_left() + 5.f, (m_rect.get_top() + m_rect.get_bottom()) / 2 - - Resources::control_font->get_height() / 2 + + Resources::small_font->get_height() / 2 + m_rect.get_height() * float(i)), FontAlignment::ALIGN_LEFT, LAYER_GUI + 6, - Color::BLACK); + Color::WHITE); } } } diff --git a/src/interface/control_textbox.cpp b/src/interface/control_textbox.cpp index 89471ad0f3..3b4d377c2c 100644 --- a/src/interface/control_textbox.cpp +++ b/src/interface/control_textbox.cpp @@ -84,13 +84,13 @@ ControlTextbox::draw(DrawingContext& context) LAYER_GUI); if (m_caret_pos != m_secondary_caret_pos) { - float lgt1 = Resources::control_font + float lgt1 = Resources::small_font ->get_text_width(get_first_chars_visible(std::max( std::min(m_caret_pos, m_secondary_caret_pos) - m_current_offset, 0 ))); - float lgt2 = Resources::control_font + float lgt2 = Resources::small_font ->get_text_width(get_first_chars_visible(std::min( std::max(m_caret_pos, m_secondary_caret_pos) - m_current_offset, int(get_contents_visible().size()) @@ -104,21 +104,21 @@ ControlTextbox::draw(DrawingContext& context) LAYER_GUI); } - context.color().draw_text(Resources::control_font, + context.color().draw_text(Resources::small_font, get_contents_visible(), Vector(m_rect.get_left() + 5.f, (m_rect.get_top() + m_rect.get_bottom()) / 2 - - Resources::control_font->get_height() / 2), + Resources::small_font->get_height() / 2), FontAlignment::ALIGN_LEFT, LAYER_GUI + 1, - Color::BLACK); + Color::WHITE); if (m_cursor_timer > 0 && m_has_focus) { - float lgt = Resources::control_font + float lgt = Resources::small_font ->get_text_width(get_first_chars_visible(m_caret_pos - m_current_offset)); context.color().draw_line(m_rect.p1() + Vector(lgt + 5.f, 2.f), m_rect.p1() + Vector(lgt + 5.f, - Resources::control_font->get_height() + 4.f), + Resources::small_font->get_height() + 4.f), Color::BLACK, LAYER_GUI + 1); } @@ -413,7 +413,7 @@ ControlTextbox::get_text_position(const Vector& pos) const float dist = pos.x - m_rect.get_left(); int i = 0; - while (Resources::control_font->get_text_width(get_first_chars_visible(i)) < dist + while (Resources::small_font->get_text_width(get_first_chars_visible(i)) < dist && i <= int(m_charlist.size())) i++; @@ -435,7 +435,7 @@ ControlTextbox::get_truncated_text(const std::string& text) const bool ControlTextbox::fits(const std::string& text) const { - return Resources::control_font->get_text_width(text) <= m_rect.get_width() - 10.f; + return Resources::small_font->get_text_width(text) <= m_rect.get_width() - 10.f; } void diff --git a/src/interface/label.cpp b/src/interface/label.cpp index 04877ea877..700f49e6cf 100644 --- a/src/interface/label.cpp +++ b/src/interface/label.cpp @@ -54,18 +54,18 @@ InterfaceLabel::on_mouse_motion(const SDL_MouseMotionEvent& motion) void InterfaceLabel::draw(DrawingContext& context) { - context.color().draw_text(Resources::control_font, + context.color().draw_text(Resources::small_font, get_truncated_text(), Vector(m_rect.get_left() + 5.f, (m_rect.get_top() + m_rect.get_bottom()) / 2 - - Resources::control_font->get_height() / 2 + 1.f), + Resources::small_font->get_height() / 2 + 1.f), FontAlignment::ALIGN_LEFT, LAYER_GUI, Color::WHITE); auto has_description = m_description.length() > 0; if ((!fits(m_label) || has_description) && m_rect.contains(m_mouse_pos)) { - auto font = Resources::control_font; + auto font = Resources::small_font; auto text_width = font->get_text_width(m_label); auto text_height = font->get_height() * (has_description ? 2 : 1); @@ -102,7 +102,7 @@ InterfaceLabel::draw(DrawingContext& context) bool InterfaceLabel::fits(const std::string& text) const { - return Resources::control_font->get_text_width(text) <= m_rect.get_width(); + return Resources::small_font->get_text_width(text) <= m_rect.get_width(); } std::string From fee288598940ac8358bf6507e63f3a6fce39567b Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 01:09:51 -0400 Subject: [PATCH 092/141] Ability to Save temporary levels --- src/editor/editor.cpp | 21 +++- src/editor/editor.hpp | 1 + src/supertux/level_parser.cpp | 2 +- src/supertux/level_parser.hpp | 2 + .../menu/editor_levelset_select_menu.cpp | 23 +++-- .../menu/editor_levelset_select_menu.hpp | 5 +- src/supertux/menu/editor_temp_save_as.cpp | 98 +++++++++++++++++++ src/supertux/menu/editor_temp_save_as.hpp | 44 +++++++++ src/supertux/menu/editor_temp_save_menu.cpp | 60 ++++++++++++ src/supertux/menu/editor_temp_save_menu.hpp | 34 +++++++ src/supertux/menu/menu_storage.cpp | 4 + src/supertux/menu/menu_storage.hpp | 1 + 12 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 src/supertux/menu/editor_temp_save_as.cpp create mode 100644 src/supertux/menu/editor_temp_save_as.hpp create mode 100644 src/supertux/menu/editor_temp_save_menu.cpp create mode 100644 src/supertux/menu/editor_temp_save_menu.hpp diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index d49dbbf6ce..5279029a66 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -159,7 +159,8 @@ Editor::Editor() : m_mouse_pos(0.f, 0.f), m_layers_widget_needs_refresh(false), m_script_manager(), - m_on_exit_cb(nullptr) + m_on_exit_cb(nullptr), + m_save_temp_level(false) { auto toolbox_widget = std::make_unique(*this); auto layers_widget = std::make_unique(*this); @@ -306,7 +307,8 @@ void Editor::level_from_nothing() { m_level = std::make_unique(false); - m_level->m_name = "Supertux Level"; + m_level->m_name = ""; + m_level->m_license = LEVEL_DEFAULT_LICENSE; m_level->m_tileset = "images/tiles.strf"; auto sector = SectorParser::from_nothing(*m_level); sector->set_name(DEFAULT_SECTOR_NAME); @@ -582,8 +584,19 @@ Editor::remove_autosave_file() bool Editor::save_level(const std::string& filename, bool switch_file) { - if (m_temp_level) - return false; + if (m_temp_level && !m_save_temp_level) + { + MenuManager::instance().set_menu(MenuStorage::EDITOR_TEMP_SAVE_MENU); + return true; + } + + if (m_save_temp_level) + { + m_save_temp_level = false; + m_temp_level = false; + // Implied + switch_file = true; + } auto file = !filename.empty() ? filename : m_levelfile; diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index dfd9ae3482..a6da2df003 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -222,6 +222,7 @@ class Editor final : public Screen, bool m_reactivate_request; bool m_deactivate_request; bool m_save_request; + bool m_save_temp_level; std::string m_save_request_filename; bool m_save_request_switch; bool m_test_request; diff --git a/src/supertux/level_parser.cpp b/src/supertux/level_parser.cpp index de448c2287..b88b04e105 100644 --- a/src/supertux/level_parser.cpp +++ b/src/supertux/level_parser.cpp @@ -224,7 +224,7 @@ LevelParser::create(const std::string& filepath, const std::string& levelname) { m_level.m_filename = filepath; m_level.m_name = levelname; - m_level.m_license = "CC-BY-SA 4.0 International"; + m_level.m_license = LEVEL_DEFAULT_LICENSE; m_level.m_tileset = m_worldmap ? "images/ice_world.strf" : "images/tiles.strf"; auto sector = SectorParser::from_nothing(m_level); diff --git a/src/supertux/level_parser.hpp b/src/supertux/level_parser.hpp index e1c46776e0..88c320a556 100644 --- a/src/supertux/level_parser.hpp +++ b/src/supertux/level_parser.hpp @@ -23,6 +23,8 @@ class Level; class ReaderDocument; class ReaderMapping; +static std::string_view LEVEL_DEFAULT_LICENSE = "CC-BY-SA 4.0 International"; + class LevelParser final { public: diff --git a/src/supertux/menu/editor_levelset_select_menu.cpp b/src/supertux/menu/editor_levelset_select_menu.cpp index 27cdecdd81..f1b04776b2 100644 --- a/src/supertux/menu/editor_levelset_select_menu.cpp +++ b/src/supertux/menu/editor_levelset_select_menu.cpp @@ -29,6 +29,7 @@ #include "supertux/level.hpp" #include "supertux/menu/editor_levelset_select_menu.hpp" #include "supertux/menu/editor_delete_levelset_menu.hpp" +#include "supertux/menu/editor_temp_save_as.hpp" #include "supertux/menu/menu_storage.hpp" #include "supertux/sector_parser.hpp" #include "supertux/world.hpp" @@ -36,8 +37,9 @@ #include "util/gettext.hpp" #include "util/log.hpp" -EditorLevelsetSelectMenu::EditorLevelsetSelectMenu() : - m_contrib_worlds() +EditorLevelsetSelectMenu::EditorLevelsetSelectMenu(bool save_as) : + m_contrib_worlds(), + m_save_as(save_as) { initialize(); } @@ -70,7 +72,7 @@ EditorLevelsetSelectMenu::initialize() return false; }); - add_label(_("Choose World")); + add_label(_(m_save_as ? "Save level as..." : "Choose World")); add_hl(); int i = 0; @@ -126,9 +128,18 @@ EditorLevelsetSelectMenu::menu_action(MenuItem& item) { if (item.get_id() >= 0) { - std::unique_ptr menu = std::unique_ptr(new EditorLevelSelectMenu( - World::from_directory(m_contrib_worlds[item.get_id()]), this)); - MenuManager::instance().push_menu(std::move(menu)); + if (m_save_as) + { + std::unique_ptr menu = std::make_unique( + World::from_directory(m_contrib_worlds[item.get_id()])); + MenuManager::instance().push_menu(std::move(menu)); + } + else + { + std::unique_ptr menu = std::unique_ptr(new EditorLevelSelectMenu( + World::from_directory(m_contrib_worlds[item.get_id()]), this)); + MenuManager::instance().push_menu(std::move(menu)); + } } else if (item.get_id() == -3) { diff --git a/src/supertux/menu/editor_levelset_select_menu.hpp b/src/supertux/menu/editor_levelset_select_menu.hpp index 1215a06091..5f892a97ff 100644 --- a/src/supertux/menu/editor_levelset_select_menu.hpp +++ b/src/supertux/menu/editor_levelset_select_menu.hpp @@ -18,13 +18,14 @@ #include "gui/menu.hpp" -class EditorLevelsetSelectMenu final : public Menu +class EditorLevelsetSelectMenu : public Menu { private: std::vector m_contrib_worlds; + bool m_save_as; public: - EditorLevelsetSelectMenu(); + EditorLevelsetSelectMenu(bool save_as = false); ~EditorLevelsetSelectMenu() override; void menu_action(MenuItem& item) override; diff --git a/src/supertux/menu/editor_temp_save_as.cpp b/src/supertux/menu/editor_temp_save_as.cpp new file mode 100644 index 0000000000..7cfa120922 --- /dev/null +++ b/src/supertux/menu/editor_temp_save_as.cpp @@ -0,0 +1,98 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "supertux/menu/editor_temp_save_as.hpp" + +#include "editor/editor.hpp" +#include "gui/dialog.hpp" +#include "gui/menu_item.hpp" +#include "gui/menu_manager.hpp" +#include "supertux/level.hpp" +#include "supertux/gameconfig.hpp" +#include "supertux/menu/menu_storage.hpp" +#include "supertux/world.hpp" +#include "util/gettext.hpp" +#include "video/compositor.hpp" + +EditorTempSaveAs::EditorTempSaveAs(std::unique_ptr world) : + m_world(std::move(world)), + m_file_name() +{ + Level* level = Editor::current()->get_level(); + add_label(_("Save Level as")); + + add_hl(); + + add_textfield(_("Level name"), &(level->m_name)); + add_textfield(_("Author"), &(level->m_author)); + add_textfield(_("License"), &(level->m_license)); + + add_hl(); + add_entry(MNID_SAVE, _("Save")); + add_entry(MNID_CANCEL, _("Cancel")); + + std::string dir; + int num = 0; + do { + num++; + m_file_name = "level" + std::to_string(num) + ".stl"; + dir = m_world->get_basedir() + "/" + m_file_name; + } while ( PHYSFS_exists(dir.c_str()) ); +} + +EditorTempSaveAs::~EditorTempSaveAs() +{ + auto editor = Editor::current(); + if (editor == nullptr) { + return; + } + editor->m_reactivate_request = true; +} + +void +EditorTempSaveAs::menu_action(MenuItem& item) +{ + auto editor = Editor::current(); + + switch (item.get_id()) + { + case MNID_SAVE: + { + Level* level = editor->get_level(); + + if (level->m_name.empty()) + { + Dialog::show_message(_("Please enter a name for this level.")); + return; + } + + editor->m_save_request = true; + editor->m_save_request_filename = m_file_name; + editor->m_save_temp_level = true; + + editor->set_world(std::move(std::unique_ptr(m_world.release()))); + MenuManager::instance().clear_menu_stack(); + } + break; + + case MNID_CANCEL: + MenuManager::instance().clear_menu_stack(); + break; + + default: + break; + } +} diff --git a/src/supertux/menu/editor_temp_save_as.hpp b/src/supertux/menu/editor_temp_save_as.hpp new file mode 100644 index 0000000000..7e67745d15 --- /dev/null +++ b/src/supertux/menu/editor_temp_save_as.hpp @@ -0,0 +1,44 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "gui/menu.hpp" + +class World; + +class EditorTempSaveAs final : public Menu +{ +private: + enum MenuIDs { + MNID_SAVE, + MNID_CANCEL + }; + +public: + EditorTempSaveAs(std::unique_ptr world); + ~EditorTempSaveAs() override; + + void menu_action(MenuItem& item) override; + +private: + std::unique_ptr m_world; + std::string m_file_name; + +private: + EditorTempSaveAs(const EditorTempSaveAs&) = delete; + EditorTempSaveAs& operator=(const EditorTempSaveAs&) = delete; +}; diff --git a/src/supertux/menu/editor_temp_save_menu.cpp b/src/supertux/menu/editor_temp_save_menu.cpp new file mode 100644 index 0000000000..0eb40bb60f --- /dev/null +++ b/src/supertux/menu/editor_temp_save_menu.cpp @@ -0,0 +1,60 @@ +// SuperTux +// Copyright (C) 2015 Hume2 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "supertux/menu/editor_temp_save_menu.hpp" + +#include + +#include "editor/editor.hpp" +#include "gui/dialog.hpp" +#include "gui/item_action.hpp" +#include "gui/item_goto.hpp" +#include "gui/item_toggle.hpp" +#include "gui/menu_manager.hpp" +#include "supertux/level.hpp" +#include "supertux/gameconfig.hpp" +#include "supertux/globals.hpp" +#include "supertux/menu/editor_levelset_select_menu.hpp" +#include "supertux/menu/editor_save_as.hpp" +#include "supertux/menu/menu_storage.hpp" +#include "util/gettext.hpp" +#include "video/compositor.hpp" + +#ifdef __EMSCRIPTEN__ +#include +#include +#endif + +EditorTempSaveMenu::EditorTempSaveMenu() : + EditorLevelsetSelectMenu(true) +{ +} + +EditorTempSaveMenu::~EditorTempSaveMenu() +{ +} + +void +EditorTempSaveMenu::initialize() +{ + EditorLevelsetSelectMenu::initialize(); +} + +void +EditorTempSaveMenu::menu_action(MenuItem& item) +{ + EditorLevelsetSelectMenu::menu_action(item); +} diff --git a/src/supertux/menu/editor_temp_save_menu.hpp b/src/supertux/menu/editor_temp_save_menu.hpp new file mode 100644 index 0000000000..e233f4e8a3 --- /dev/null +++ b/src/supertux/menu/editor_temp_save_menu.hpp @@ -0,0 +1,34 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "gui/menu.hpp" +#include "supertux/menu/editor_levelset_select_menu.hpp" + +class EditorTempSaveMenu final : public EditorLevelsetSelectMenu +{ +public: + EditorTempSaveMenu(); + ~EditorTempSaveMenu() override; + + void menu_action(MenuItem& item) override; + void initialize(); + +private: + EditorTempSaveMenu(const EditorTempSaveMenu&) = delete; + EditorTempSaveMenu& operator=(const EditorTempSaveMenu&) = delete; +}; diff --git a/src/supertux/menu/menu_storage.cpp b/src/supertux/menu/menu_storage.cpp index 7c3e189407..7b16ec97f6 100644 --- a/src/supertux/menu/menu_storage.cpp +++ b/src/supertux/menu/menu_storage.cpp @@ -31,6 +31,7 @@ #include "supertux/menu/editor_levelset_select_menu.hpp" #include "supertux/menu/editor_new_levelset_menu.hpp" #include "supertux/menu/editor_objectgroup_menu.hpp" +#include "supertux/menu/editor_temp_save_menu.hpp" #include "supertux/menu/editor_tilegroup_menu.hpp" #include "supertux/menu/editor_sector_menu.hpp" #include "supertux/menu/editor_sectors_menu.hpp" @@ -150,6 +151,9 @@ MenuStorage::create(MenuId menu_id) case EDITOR_MENU: return std::make_unique(); + + case EDITOR_TEMP_SAVE_MENU: + return std::make_unique(); case EDITOR_TILEGROUP_MENU: return std::make_unique(); diff --git a/src/supertux/menu/menu_storage.hpp b/src/supertux/menu/menu_storage.hpp index 31a15945c9..26982c55c4 100644 --- a/src/supertux/menu/menu_storage.hpp +++ b/src/supertux/menu/menu_storage.hpp @@ -54,6 +54,7 @@ class MenuStorage final EDITOR_NEW_LEVELSET_MENU, EDITOR_LEVEL_SELECT_MENU, EDITOR_MENU, + EDITOR_TEMP_SAVE_MENU, EDITOR_TILEGROUP_MENU, EDITOR_OBJECTGROUP_MENU, EDITOR_SECTORS_MENU, From 2a45860beca873a30f940c3d994aff93a048a346 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 01:17:00 -0400 Subject: [PATCH 093/141] Notification on level create/change saving --- src/editor/editor.cpp | 12 +++++++----- src/supertux/menu/editor_temp_save_as.cpp | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 5279029a66..231f118917 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -199,10 +199,12 @@ Editor::Editor() : auto save_button = std::make_unique("images/engine/editor/save.png", Vector(128, 0), [this] { - bool saved = save_level(); - auto notif = std::make_unique("save_level_notif", false, true); - notif->set_text(saved ? _("Level saved!") : _("Level failed to save.")); - MenuManager::instance().set_notification(std::move(notif)); + if (save_level()) + { + auto notif = std::make_unique("save_level_notif", false, true); + notif->set_text(_("Level saved!")); + MenuManager::instance().set_notification(std::move(notif)); + } } ); save_button->set_help_text(_("Save level")); @@ -587,7 +589,7 @@ Editor::save_level(const std::string& filename, bool switch_file) if (m_temp_level && !m_save_temp_level) { MenuManager::instance().set_menu(MenuStorage::EDITOR_TEMP_SAVE_MENU); - return true; + return false; } if (m_save_temp_level) diff --git a/src/supertux/menu/editor_temp_save_as.cpp b/src/supertux/menu/editor_temp_save_as.cpp index 7cfa120922..7be77df329 100644 --- a/src/supertux/menu/editor_temp_save_as.cpp +++ b/src/supertux/menu/editor_temp_save_as.cpp @@ -20,6 +20,7 @@ #include "gui/dialog.hpp" #include "gui/menu_item.hpp" #include "gui/menu_manager.hpp" +#include "gui/notification.hpp" #include "supertux/level.hpp" #include "supertux/gameconfig.hpp" #include "supertux/menu/menu_storage.hpp" @@ -84,6 +85,11 @@ EditorTempSaveAs::menu_action(MenuItem& item) editor->m_save_temp_level = true; editor->set_world(std::move(std::unique_ptr(m_world.release()))); + + auto notif = std::make_unique("create_level_notif", false, true); + notif->set_text(_("Level created!")); + MenuManager::instance().set_notification(std::move(notif)); + MenuManager::instance().clear_menu_stack(); } break; From 2f84c3d8d6a8ff1b9957d4f74a18ead7a0606645 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 01:28:14 -0400 Subject: [PATCH 094/141] Move Editor settings into own submenu Requested by FrostC --- src/supertux/menu/editor_menu.cpp | 26 +--------- src/supertux/menu/editor_settings.cpp | 63 +++++++++++++++++++++++ src/supertux/menu/editor_settings.hpp | 39 ++++++++++++++ src/supertux/menu/editor_temp_save_as.cpp | 6 +-- src/supertux/menu/editor_temp_save_as.hpp | 3 +- src/supertux/menu/menu_storage.cpp | 4 ++ src/supertux/menu/menu_storage.hpp | 1 + 7 files changed, 111 insertions(+), 31 deletions(-) create mode 100644 src/supertux/menu/editor_settings.cpp create mode 100644 src/supertux/menu/editor_settings.hpp diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index 499aed431f..e58dcf5e75 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -49,11 +49,6 @@ EditorMenu::refresh() bool worldmap = Editor::current()->get_level()->is_worldmap(); bool is_world = Editor::current()->get_world() != nullptr; - std::vector snap_grid_sizes; - snap_grid_sizes.push_back(_("tiny tile (4px)")); - snap_grid_sizes.push_back(_("small tile (8px)")); - snap_grid_sizes.push_back(_("medium tile (16px)")); - snap_grid_sizes.push_back(_("big tile (32px)")); add_label(_("Level Editor")); add_hl(); @@ -85,25 +80,6 @@ EditorMenu::refresh() add_submenu(_("Convert Tiles"), MenuStorage::EDITOR_CONVERTERS_MENU) .set_help(_("Convert all tiles in the level using converters.")); - add_hl(); - - add_string_select(-1, _("Grid Size"), &(g_config->editor_selected_snap_grid_size), snap_grid_sizes); - add_toggle(-1, _("Show Grid"), &(g_config->editor_render_grid)); - add_toggle(-1, _("Grid Snapping"), &(g_config->editor_snap_to_grid)); - add_toggle(-1, _("Render Background"), &(g_config->editor_render_background)); - add_toggle(-1, _("Render Light"), &(Compositor::s_render_lighting)); - add_toggle(-1, _("Autotile Mode"), &(g_config->editor_autotile_mode)); - add_toggle(-1, _("Enable Autotile Help"), &(g_config->editor_autotile_help)); - add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); - add_toggle(-1, _("Show properties sidebar"), &(g_config->editor_show_properties_sidebar)); - add_toggle(-1, _("Show toolbar"), &(g_config->editor_show_toolbar_widgets)); - add_textfield(_("Default editor"), &(g_config->preferred_text_editor)); - if (g_config->editor_undo_tracking) - { - add_intfield(_("Undo Stack Size"), &(g_config->editor_undo_stack_size), -1, true); - } - add_intfield(_("Autosave Frequency"), &(g_config->editor_autosave_frequency)); - if (Editor::current()->has_deprecated_tiles()) { add_hl(); @@ -115,6 +91,8 @@ EditorMenu::refresh() } add_hl(); + + add_submenu(_("Editor settings"), MenuStorage::EDITOR_SETTINGS_MENU); add_submenu(worldmap ? _("Worldmap Settings") : _("Level Settings"), MenuStorage::EDITOR_LEVEL_MENU); diff --git a/src/supertux/menu/editor_settings.cpp b/src/supertux/menu/editor_settings.cpp new file mode 100644 index 0000000000..f991035ca3 --- /dev/null +++ b/src/supertux/menu/editor_settings.cpp @@ -0,0 +1,63 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "supertux/menu/editor_settings.hpp" + +#include "util/gettext.hpp" +#include "supertux/gameconfig.hpp" +#include "supertux/globals.hpp" +#include "video/compositor.hpp" + +EditorSettings::EditorSettings() +{ + add_label(_("Editor Settings")); + add_hl(); + + std::vector snap_grid_sizes; + snap_grid_sizes.push_back(_("tiny tile (4px)")); + snap_grid_sizes.push_back(_("small tile (8px)")); + snap_grid_sizes.push_back(_("medium tile (16px)")); + snap_grid_sizes.push_back(_("big tile (32px)")); + + add_string_select(-1, _("Grid Size"), &(g_config->editor_selected_snap_grid_size), snap_grid_sizes); + add_toggle(-1, _("Show Grid"), &(g_config->editor_render_grid)); + add_toggle(-1, _("Grid Snapping"), &(g_config->editor_snap_to_grid)); + add_toggle(-1, _("Render Background"), &(g_config->editor_render_background)); + add_toggle(-1, _("Render Light"), &(Compositor::s_render_lighting)); + add_toggle(-1, _("Autotile Mode"), &(g_config->editor_autotile_mode)); + add_toggle(-1, _("Enable Autotile Help"), &(g_config->editor_autotile_help)); + add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); + add_toggle(-1, _("Show Properties Sidebar"), &(g_config->editor_show_properties_sidebar)); + add_toggle(-1, _("Show Toolbar"), &(g_config->editor_show_toolbar_widgets)); + add_textfield(_("Preferred Text Editor"), &(g_config->preferred_text_editor)); + if (g_config->editor_undo_tracking) + { + add_intfield(_("Undo Stack Size"), &(g_config->editor_undo_stack_size), -1, true); + } + add_intfield(_("Autosave Frequency"), &(g_config->editor_autosave_frequency)); + + add_hl(); + add_back(_("Back")); +} + +EditorSettings::~EditorSettings() +{ +} + +void +EditorSettings::menu_action(MenuItem& item) +{ +} diff --git a/src/supertux/menu/editor_settings.hpp b/src/supertux/menu/editor_settings.hpp new file mode 100644 index 0000000000..b2326714ff --- /dev/null +++ b/src/supertux/menu/editor_settings.hpp @@ -0,0 +1,39 @@ +// SuperTux +// Copyright (C) 2025 Hyland B. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "gui/menu.hpp" + +class World; + +class EditorSettings final : public Menu +{ +private: + enum MenuIDs { + MNID_CANCEL + }; + +public: + EditorSettings(); + ~EditorSettings() override; + + void menu_action(MenuItem& item) override; + +private: + EditorSettings(const EditorSettings&) = delete; + EditorSettings& operator=(const EditorSettings&) = delete; +}; diff --git a/src/supertux/menu/editor_temp_save_as.cpp b/src/supertux/menu/editor_temp_save_as.cpp index 7be77df329..aad53fb95f 100644 --- a/src/supertux/menu/editor_temp_save_as.cpp +++ b/src/supertux/menu/editor_temp_save_as.cpp @@ -43,7 +43,7 @@ EditorTempSaveAs::EditorTempSaveAs(std::unique_ptr world) : add_hl(); add_entry(MNID_SAVE, _("Save")); - add_entry(MNID_CANCEL, _("Cancel")); + add_back(_("Cancel")); std::string dir; int num = 0; @@ -94,10 +94,6 @@ EditorTempSaveAs::menu_action(MenuItem& item) } break; - case MNID_CANCEL: - MenuManager::instance().clear_menu_stack(); - break; - default: break; } diff --git a/src/supertux/menu/editor_temp_save_as.hpp b/src/supertux/menu/editor_temp_save_as.hpp index 7e67745d15..6f4b61ef97 100644 --- a/src/supertux/menu/editor_temp_save_as.hpp +++ b/src/supertux/menu/editor_temp_save_as.hpp @@ -24,8 +24,7 @@ class EditorTempSaveAs final : public Menu { private: enum MenuIDs { - MNID_SAVE, - MNID_CANCEL + MNID_SAVE }; public: diff --git a/src/supertux/menu/menu_storage.cpp b/src/supertux/menu/menu_storage.cpp index 7b16ec97f6..c417865cda 100644 --- a/src/supertux/menu/menu_storage.cpp +++ b/src/supertux/menu/menu_storage.cpp @@ -31,6 +31,7 @@ #include "supertux/menu/editor_levelset_select_menu.hpp" #include "supertux/menu/editor_new_levelset_menu.hpp" #include "supertux/menu/editor_objectgroup_menu.hpp" +#include "supertux/menu/editor_settings.hpp" #include "supertux/menu/editor_temp_save_menu.hpp" #include "supertux/menu/editor_tilegroup_menu.hpp" #include "supertux/menu/editor_sector_menu.hpp" @@ -152,6 +153,9 @@ MenuStorage::create(MenuId menu_id) case EDITOR_MENU: return std::make_unique(); + case EDITOR_SETTINGS_MENU: + return std::make_unique(); + case EDITOR_TEMP_SAVE_MENU: return std::make_unique(); diff --git a/src/supertux/menu/menu_storage.hpp b/src/supertux/menu/menu_storage.hpp index 26982c55c4..257185cb34 100644 --- a/src/supertux/menu/menu_storage.hpp +++ b/src/supertux/menu/menu_storage.hpp @@ -54,6 +54,7 @@ class MenuStorage final EDITOR_NEW_LEVELSET_MENU, EDITOR_LEVEL_SELECT_MENU, EDITOR_MENU, + EDITOR_SETTINGS_MENU, EDITOR_TEMP_SAVE_MENU, EDITOR_TILEGROUP_MENU, EDITOR_OBJECTGROUP_MENU, From d35de9b37c3bf623cd9fe4fb71c4eb52bfc27039 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 01:30:31 -0400 Subject: [PATCH 095/141] Get rid of Share Level button RIP forums --- src/supertux/menu/editor_menu.cpp | 14 ++------------ src/supertux/menu/editor_menu.hpp | 1 - 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index e58dcf5e75..6fba8a1996 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -64,8 +64,6 @@ EditorMenu::refresh() add_entry(MNID_OPTIONS, _("Options")); - add_entry(MNID_SHARE, _("Share Level")); - add_entry(MNID_PACK, _("Package Add-On")); add_entry(MNID_OPEN_DIR, _("Open Level Directory")); @@ -166,15 +164,7 @@ EditorMenu::menu_action(MenuItem& item) MenuManager::instance().push_menu(MenuStorage::OPTIONS_MENU); break; - case MNID_SHARE: - { - Dialog::show_confirmation(_("We encourage you to share your levels in the SuperTux forum.\nTo find your level, click the\n\"Open Level directory\" menu item.\nDo you want to go to the forum now?"), [] { - FileSystem::open_url("https://groups.f-hub.org/supertux"); - }); - } - break; - - case MNID_HELP: + case MNID_HELP: { auto dialog = std::make_unique(); auto help_dialog_text = @@ -211,7 +201,7 @@ EditorMenu::menu_action(MenuItem& item) dialog->add_cancel_button(_("Got it!")); MenuManager::instance().set_dialog(std::move(dialog)); } - break; + break; case MNID_LEVELSEL: editor->check_unsaved_changes([] { diff --git a/src/supertux/menu/editor_menu.hpp b/src/supertux/menu/editor_menu.hpp index e9ed565662..795d33845f 100644 --- a/src/supertux/menu/editor_menu.hpp +++ b/src/supertux/menu/editor_menu.hpp @@ -30,7 +30,6 @@ class EditorMenu final : public Menu MNID_OPTIONS, MNID_PACK, MNID_OPEN_DIR, - MNID_SHARE, MNID_LEVELSEL, MNID_LEVELSETSEL, MNID_HELP, From 7eae4275897dc36569119042f5b6449826f994d1 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 01:36:08 -0400 Subject: [PATCH 096/141] Update that stupid fucking help menu --- src/supertux/menu/editor_menu.cpp | 44 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index 6fba8a1996..018fb1ace0 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -168,34 +168,34 @@ EditorMenu::menu_action(MenuItem& item) { auto dialog = std::make_unique(); auto help_dialog_text = - _("Keyboard Shortcuts:") + "\n" + - "---------------------" + "\n" + - _("Esc = Open Menu") + "\n" + - _("Ctrl+S = Save") + "\n" + - _("Ctrl+T = Test") + "\n" + - _("Ctrl+Z = Undo") + "\n" + - _("Ctrl+Y = Redo") + "\n" + - _("F5 = Toggle Autotiling") + "\n" + - _("F6 = Render Light") + "\n" + - _("F7 = Grid Snapping") + "\n" + - _("F8 = Show Grid") + "\n" + - _("Ctrl++ or Ctrl+Scroll Up = Zoom In") + "\n" + - _("Ctrl+- or Ctrl+Scroll Down = Zoom Out") + "\n" + - _("Ctrl+D = Reset Zoom") + "\n\n" + - _("Scripting Shortcuts:") + "\n" + - "-------------" + "\n" + + _("Keyboard Shortcuts") + ":\n" + + "Esc = " + _("Open Menu") + "\n" + + "Ctrl+S = " + _("Save") + "\n" + + "Ctrl+T = " + _("Test") + "\n" + + "Ctrl+Shift+T = " + _("Test at Cursor") + "\n" + + "Ctrl+Z = " + _("Undo") + "\n" + + "Ctrl+Y = " + _("Redo") + "\n" + + "F5 = " + _("Toggle Autotiling") + "\n" + + "F6 = " + _("Render Light") + "\n" + + "F7 = " + _("Grid Snapping") + "\n" + + "F8 = " + _("Show Grid") + "\n" + + "Ctrl+X = " + _("Toggle Between Tileset/Objects Tool") + "\n" + + _("Ctrl+PLUS or Ctrl+Scroll Up = Zoom In") + "\n" + + _("Ctrl+MINUS or Ctrl+Scroll Down = Zoom Out") + "\n" + + "Ctrl+D = " + _("Reset Zoom") + "\n\n" + + _("Scripting Shortcuts") + ":\n" + _("Home = Go to beginning of line") + "\n" + _("End = Go to end of line") + "\n" + _("Left arrow = Go back in text") + "\n" + _("Right arrow = Go forward in text") + "\n" + _("Backspace = Delete in front of text cursor") + "\n" + _("Delete = Delete behind text cursor") + "\n" + - _("Ctrl+X = Cut whole line") + "\n" + - _("Ctrl+C = Copy whole line") + "\n" + - _("Ctrl+V = Paste") + "\n" + - _("Ctrl+D = Duplicate line") + "\n" + - _("Ctrl+Z = Undo") + "\n" + - _("Ctrl+Y = Redo"); + "Ctrl+X = " + _("Cut whole line") + "\n" + + "Ctrl+C = " + _("Copy whole line") + "\n" + + "Ctrl+V = " + _("Paste") + "\n" + + "Ctrl+D = " + _("Duplicate line") + "\n" + + "Ctrl+Z = " + _("Undo") + "\n" + + "Ctrl+Y = " + _("Redo"); dialog->set_text(help_dialog_text); dialog->add_cancel_button(_("Got it!")); From 2da2525a40b52f4408cbb5e8efbb9e60dde539f4 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 01:41:46 -0400 Subject: [PATCH 097/141] Hide unnecessary buttons (some of which crash the game) --- src/editor/editor.hpp | 2 ++ src/supertux/menu/editor_menu.cpp | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index a6da2df003..9fed81a4e5 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -119,6 +119,8 @@ class Editor final : public Screen, } std::string get_level_directory() const; + + inline bool is_temp_level() const { return m_temp_level; } void open_level_directory(); diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index 018fb1ace0..a561813844 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -49,6 +49,7 @@ EditorMenu::refresh() bool worldmap = Editor::current()->get_level()->is_worldmap(); bool is_world = Editor::current()->get_world() != nullptr; + bool is_temp_level = Editor::current()->is_temp_level(); add_label(_("Level Editor")); add_hl(); @@ -56,7 +57,8 @@ EditorMenu::refresh() add_entry(MNID_SAVELEVEL, worldmap ? _("Save Worldmap") : _("Save Level")); if (!worldmap) { add_entry(MNID_SAVEASLEVEL, _("Save Level as")); - add_entry(MNID_SAVECOPYLEVEL, _("Save Copy")); + if (!is_temp_level) + add_entry(MNID_SAVECOPYLEVEL, _("Save Copy")); add_entry(MNID_TESTLEVEL, _("Test Level")); } else { add_entry(MNID_TESTLEVEL, _("Test Worldmap")); @@ -64,19 +66,24 @@ EditorMenu::refresh() add_entry(MNID_OPTIONS, _("Options")); - add_entry(MNID_PACK, _("Package Add-On")); - - add_entry(MNID_OPEN_DIR, _("Open Level Directory")); + if (!is_temp_level) + { + add_entry(MNID_PACK, _("Package Add-On")); + add_entry(MNID_OPEN_DIR, _("Open Level Directory")); + } - if (is_world) + if (is_world && !is_temp_level) add_entry(MNID_LEVELSEL, _("Edit Another Level")); add_entry(MNID_LEVELSETSEL, _("Edit Another World")); - add_hl(); + if (!is_temp_level) + { + add_hl(); - add_submenu(_("Convert Tiles"), MenuStorage::EDITOR_CONVERTERS_MENU) - .set_help(_("Convert all tiles in the level using converters.")); + add_submenu(_("Convert Tiles"), MenuStorage::EDITOR_CONVERTERS_MENU) + .set_help(_("Convert all tiles in the level using converters.")); + } if (Editor::current()->has_deprecated_tiles()) { From 43bf49d376b66cf62350d1cea88fbb5cb07002f1 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 02:26:05 -0400 Subject: [PATCH 098/141] Refactor editor toolbar initialization code This should probably be moved to another class, honestly. --- src/editor/button_widget.cpp | 12 ++ src/editor/button_widget.hpp | 13 +- src/editor/editor.cpp | 223 ++++++++++++++++++----------------- 3 files changed, 142 insertions(+), 106 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index db38f9a4b0..2973af0543 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -37,6 +37,18 @@ ButtonWidget::ButtonWidget(SpritePtr sprite, const Vector& pos, { } +void +ButtonWidget::set_position(const Vector& pos) +{ + float w = m_rect.get_width(), + h = m_rect.get_height(); + + m_rect.set_left(pos.x); + m_rect.set_width(w); + m_rect.set_top(pos.y); + m_rect.set_height(h); +} + void ButtonWidget::draw(DrawingContext& context) { diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index 9433489204..c3f22c9ad6 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -33,6 +33,7 @@ class ButtonWidget : public Widget { } + virtual void draw(DrawingContext& context) override; virtual void update(float dt_sec) override; @@ -43,12 +44,13 @@ class ButtonWidget : public Widget virtual bool on_mouse_button_down(const SDL_MouseButtonEvent& button) override; virtual bool on_mouse_motion(const SDL_MouseMotionEvent& motion) override; + void set_position(const Vector& pos); void set_sprite(const std::string& path); void set_sprite(SpritePtr sprite); void set_help_text(const std::string& help_text); -private: +protected: SpritePtr m_sprite; Rectf m_rect; bool m_grab; @@ -80,6 +82,15 @@ class EditorToolbarButtonWidget : public ButtonWidget m_visible(true) { } + + EditorToolbarButtonWidget(const std::string& path, std::function callback = {}, std::string help_text = "", std::optional sprite_size = std::nullopt) : + ButtonWidget(SpriteManager::current()->create(path), Vector(0,0), std::move(callback), std::move(sprite_size)), + m_tile_mode_visible(true), + m_object_mode_visible(true), + m_visible(true) + { + set_help_text(help_text); + } virtual void draw(DrawingContext& context) override { diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 231f118917..78a151877a 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -174,118 +174,131 @@ Editor::Editor() : m_widgets.push_back(std::move(layers_widget)); m_widgets.push_back(std::move(overlay_widget)); - auto grid_size_widget = std::make_unique("images/engine/editor/grid_button.png", - Vector(64, 0), - [this] { - auto& snap_grid_size = g_config->editor_selected_snap_grid_size; - if (snap_grid_size == 0) - { - if(!g_config->editor_render_grid) + std::array, 5> general_widgets = { + // Grid button + std::make_unique("images/engine/editor/grid_button.png", + [this] { + auto& snap_grid_size = g_config->editor_selected_snap_grid_size; + if (snap_grid_size == 0) { - snap_grid_size = 3; + if(!g_config->editor_render_grid) + { + snap_grid_size = 3; + } + g_config->editor_render_grid = !g_config->editor_render_grid; } - g_config->editor_render_grid = !g_config->editor_render_grid; - } - else - snap_grid_size--; - }); - grid_size_widget->set_help_text(_("Change / Toggle grid size")); - m_widgets.insert(m_widgets.begin() + 2, std::move(grid_size_widget)); - - auto play_button = std::make_unique("images/engine/editor/play_button.png", - Vector(96, 0), [this] { m_test_request = true; }); - play_button->set_help_text(_("Test level")); - m_widgets.insert(m_widgets.begin() + 3, std::move(play_button)); - - auto save_button = std::make_unique("images/engine/editor/save.png", - Vector(128, 0), [this] { - if (save_level()) - { - auto notif = std::make_unique("save_level_notif", false, true); - notif->set_text(_("Level saved!")); - MenuManager::instance().set_notification(std::move(notif)); - } - } - ); - save_button->set_help_text(_("Save level")); - m_widgets.insert(m_widgets.begin() + 4, std::move(save_button)); - - auto mode_button = std::make_unique("images/engine/editor/toggle_tile_object_mode.png", - Vector(160, 0), [this] { - toggle_tile_object_mode(); - } - ); - mode_button->set_help_text(_("Toggle between object and tile mode")); - m_widgets.insert(m_widgets.begin() + 5, std::move(mode_button)); - - auto mouse_select_button = std::make_unique( - "images/engine/editor/arrow.png", Vector(192, 0), [this]() { - m_toolbox_widget->set_mouse_tool(); - } - ); - mouse_select_button->set_help_text(_("Toggle between add and remove mode")); + else + snap_grid_size--; + }, + _("Change / Toggle grid size")), + + // Play button + std::make_unique("images/engine/editor/play_button.png", + [this] { m_test_request = true; }, + _("Test level")), + + // Save button + std::make_unique("images/engine/editor/save.png", + [this] { + if (save_level()) + { + auto notif = std::make_unique("save_level_notif", false, true); + notif->set_text(_("Level saved!")); + MenuManager::instance().set_notification(std::move(notif)); + } + }, + _("Save level")), + + // Mode button + std::make_unique("images/engine/editor/toggle_tile_object_mode.png", + [this] { + toggle_tile_object_mode(); + }, + _("Toggle between object and tile mode")), + + // Mouse select button + std::make_unique("images/engine/editor/arrow.png", + [this]() { + m_toolbox_widget->set_mouse_tool(); + }, + _("Toggle between add and remove mode")) + }; - /** - * ============= Tools only applicable for Tile mode ===================== - */ - auto select_mode_mouse_button = std::make_unique( - "images/engine/editor/select-mode0.png", Vector(224, 0), [this] { + std::array, 4> tile_mode_widgets = { + // Select mode mouse + std::make_unique("images/engine/editor/select-mode0.png", + [this] { m_toolbox_widget->set_tileselect_select_mode(0); - }); - select_mode_mouse_button->set_help_text(_("Draw mode (The current tool applies to the tile under the mouse)")); - select_mode_mouse_button->set_visible_in_object_mode(false); - select_mode_mouse_button->set_visible(false); + }, + _("Draw mode (The current tool applies to the tile under the mouse)")), + + // Select mode area + std::make_unique("images/engine/editor/select-mode1.png", + [this] { + m_toolbox_widget->set_tileselect_select_mode(1); + }, + _("Box draw mode (The current tool applies to an area / box drawn with the mouse)")), + + // Select mode fill button + std::make_unique("images/engine/editor/select-mode2.png", + [this] { + m_toolbox_widget->set_tileselect_select_mode(2); + }, + _("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")), + + // Select mode same button + std::make_unique("images/engine/editor/select-mode3.png", + [this] { + m_toolbox_widget->set_tileselect_select_mode(3); + }, + _("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")), + }; - auto select_mode_area_button = std::make_unique( - "images/engine/editor/select-mode1.png", Vector(256, 0), [this] { - m_toolbox_widget->set_tileselect_select_mode(1); - }); - select_mode_area_button->set_help_text(_("Box draw mode (The current tool applies to an area / box drawn with the mouse)")); - select_mode_area_button->set_visible_in_object_mode(false); - select_mode_area_button->set_visible(false); + std::array, 2> object_mode_widgets = { + // Select mode + std::make_unique("images/engine/editor/move-mode0.png", + [this] { + m_toolbox_widget->set_tileselect_move_mode(0); + }, + _("Select mode (Clicking selects the object under the mouse)")), + + // Duplicate mode + std::make_unique("images/engine/editor/move-mode1.png", + [this] { + m_toolbox_widget->set_tileselect_move_mode(1); + }, + _("Duplicate mode (Clicking duplicates the object under the mouse)")), + }; - auto select_mode_fill_button = std::make_unique( - "images/engine/editor/select-mode2.png", Vector(288, 0), [this] { - m_toolbox_widget->set_tileselect_select_mode(2); - }); - select_mode_fill_button->set_help_text(_("Fill mode (The current tool applies to the empty area in the enclosed space that was clicked)")); - select_mode_fill_button->set_visible_in_object_mode(false); - select_mode_fill_button->set_visible(false); + size_t i = 2; + for (auto &widget : general_widgets) + { + Vector pos(32 * (i), 0); + widget->set_position(pos); + m_widgets.insert(m_widgets.begin() + i, std::move(widget)); + ++i; + } - auto select_mode_same_button = std::make_unique( - "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { - m_toolbox_widget->set_tileselect_select_mode(3); - }); - select_mode_same_button->set_help_text(_("Replace mode (The current tool applies to all tiles that are the same tile as the one under the mouse)")); - select_mode_same_button->set_visible_in_object_mode(false); - select_mode_same_button->set_visible(false); - - /** - * ============= Tile tools end / Object tools begin ===================== - */ - auto select_mode = std::make_unique( - "images/engine/editor/move-mode0.png", Vector(224, 0), [this] { - m_toolbox_widget->set_tileselect_move_mode(0); - }); - select_mode->set_help_text(_("Select mode (Clicking selects the object under the mouse)")); - select_mode->set_visible_in_tile_mode(false); - select_mode->set_visible(false); - - auto duplicate_mode = std::make_unique( - "images/engine/editor/move-mode1.png", Vector(256, 0), [this] { - m_toolbox_widget->set_tileselect_move_mode(1); - }); - duplicate_mode->set_help_text(_("Duplicate mode (Clicking duplicates the object under the mouse)")); - duplicate_mode->set_visible_in_tile_mode(false); - duplicate_mode->set_visible(false); - - m_widgets.insert(m_widgets.begin() + 6, std::move(mouse_select_button)); - m_widgets.insert(m_widgets.begin() + 7, std::move(select_mode_mouse_button)); - m_widgets.insert(m_widgets.begin() + 8, std::move(select_mode_area_button)); - m_widgets.insert(m_widgets.begin() + 9, std::move(select_mode_fill_button)); - m_widgets.insert(m_widgets.begin() + 10, std::move(select_mode_same_button)); - m_widgets.insert(m_widgets.begin() + 11, std::move(select_mode)); - m_widgets.insert(m_widgets.begin() + 12, std::move(duplicate_mode)); + size_t mode_i = i; + for (auto &widget : tile_mode_widgets) + { + Vector pos(32 * (i), 0); + widget->set_position(pos); + widget->set_visible_in_object_mode(false); + widget->set_visible(false); + m_widgets.insert(m_widgets.begin() + i, std::move(widget)); + ++i; + } + + for (auto &widget : object_mode_widgets) + { + Vector pos(32 * (i-tile_mode_widgets.size()), 0); + widget->set_position(pos); + widget->set_visible_in_tile_mode(false); + widget->set_visible(false); + m_widgets.insert(m_widgets.begin() + i, std::move(widget)); + ++i; + } // auto code_widget = std::make_unique( // "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { From d876fe501f5b0e3e3c986f61bbd4a36abf25ecdb Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 03:04:29 -0400 Subject: [PATCH 099/141] Always show undo/redo and fade buttons instead of hiding --- src/editor/button_widget.cpp | 13 +++-- src/editor/button_widget.hpp | 4 ++ src/editor/editor.cpp | 75 ++++++++++++++-------------- src/editor/editor.hpp | 2 + src/supertux/game_object_manager.cpp | 17 +++++++ src/supertux/game_object_manager.hpp | 2 + 6 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index 2973af0543..3c26e94def 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -33,7 +33,8 @@ ButtonWidget::ButtonWidget(SpritePtr sprite, const Vector& pos, m_hover(false), m_sig_click(std::move(sig_click)), m_mouse_pos(), - m_help_text() + m_help_text(), + m_disabled(false) { } @@ -55,14 +56,17 @@ ButtonWidget::draw(DrawingContext& context) context.color().draw_filled_rect(m_rect, Color(0.0f, 0.0f, 0.0f, 0.6f), 4.0f, LAYER_GUI-5); + std::cout << m_disabled << std::endl; if (m_sprite) { + if (m_disabled) context.set_alpha(0.4); m_sprite->draw_scaled(context.color(), m_rect.grown(-4.0f), LAYER_GUI - 5); + if (m_disabled) context.set_alpha(1.0); } if (m_grab) { context.color().draw_filled_rect(m_rect, g_config->editorgrabcolor, 4.0f, LAYER_GUI-5); - } else if (m_hover) { + } else if (m_hover && !m_disabled) { context.color().draw_filled_rect(m_rect, g_config->editorhovercolor, 4.0f, LAYER_GUI-5); } @@ -90,7 +94,8 @@ bool ButtonWidget::on_mouse_button_up(const SDL_MouseButtonEvent& button) { if (button.button != SDL_BUTTON_LEFT) return false; - + if (m_disabled) return true; + m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); if (m_grab) { @@ -110,7 +115,7 @@ ButtonWidget::on_mouse_button_up(const SDL_MouseButtonEvent& button) bool ButtonWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) { - if (button.button != SDL_BUTTON_LEFT) return false; + if (button.button != SDL_BUTTON_LEFT || m_disabled) return false; m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index c3f22c9ad6..c7f58cea2c 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -49,12 +49,16 @@ class ButtonWidget : public Widget void set_sprite(SpritePtr sprite); void set_help_text(const std::string& help_text); + + inline void set_disabled(bool disabled) { m_disabled = disabled; } + inline bool is_disabled() { return m_disabled; } protected: SpritePtr m_sprite; Rectf m_rect; bool m_grab; bool m_hover; + bool m_disabled; std::function m_sig_click; Vector m_mouse_pos; std::string m_help_text; diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 78a151877a..3da3800f11 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -18,6 +18,7 @@ #include "gui/notification.hpp" #include +#include #include #include #include @@ -174,7 +175,18 @@ Editor::Editor() : m_widgets.push_back(std::move(layers_widget)); m_widgets.push_back(std::move(overlay_widget)); - std::array, 5> general_widgets = { + std::array, 7> general_widgets = { + // Undo button + std::make_unique("images/engine/editor/undo.png", + std::bind(&Editor::undo, this), + _("Undo"), + Sizef(32.f, 32.f)), + + std::make_unique("images/engine/editor/redo.png", + std::bind(&Editor::redo, this), + _("Redo"), + Sizef(32.f, 32.f)), + // Grid button std::make_unique("images/engine/editor/grid_button.png", [this] { @@ -211,9 +223,7 @@ Editor::Editor() : // Mode button std::make_unique("images/engine/editor/toggle_tile_object_mode.png", - [this] { - toggle_tile_object_mode(); - }, + std::bind(&Editor::toggle_tile_object_mode, this), _("Toggle between object and tile mode")), // Mouse select button @@ -273,16 +283,15 @@ Editor::Editor() : size_t i = 2; for (auto &widget : general_widgets) { - Vector pos(32 * (i), 0); + Vector pos(32 * (i-2), 0); widget->set_position(pos); m_widgets.insert(m_widgets.begin() + i, std::move(widget)); ++i; } - size_t mode_i = i; for (auto &widget : tile_mode_widgets) { - Vector pos(32 * (i), 0); + Vector pos(32 * (i-2), 0); widget->set_position(pos); widget->set_visible_in_object_mode(false); widget->set_visible(false); @@ -292,13 +301,18 @@ Editor::Editor() : for (auto &widget : object_mode_widgets) { - Vector pos(32 * (i-tile_mode_widgets.size()), 0); + Vector pos(32 * (i-tile_mode_widgets.size()-2), 0); widget->set_position(pos); widget->set_visible_in_tile_mode(false); widget->set_visible(false); m_widgets.insert(m_widgets.begin() + i, std::move(widget)); ++i; } + + m_undo_widget = reinterpret_cast(m_widgets[2].get()); + m_redo_widget = reinterpret_cast(m_widgets[3].get()); + m_undo_widget->set_disabled(true); + m_redo_widget->set_disabled(true); // auto code_widget = std::make_unique( // "images/engine/editor/select-mode3.png", Vector(320, 0), [this] { @@ -1443,35 +1457,8 @@ Editor::check_save_prerequisites(const std::function& callback) const void Editor::retoggle_undo_tracking() { - if (g_config->editor_undo_tracking && !m_undo_widget) - { - // Add undo/redo button widgets. - auto undo_button_widget = std::make_unique("images/engine/editor/undo.png", - Vector(0, 0), [this]{ undo(); }, Sizef(32.f, 32.f)); - undo_button_widget->set_help_text(_("Undo")); - auto redo_button_widget = std::make_unique("images/engine/editor/redo.png", - Vector(32, 0), [this]{ redo(); }, Sizef(32.f, 32.f)); - redo_button_widget->set_help_text(_("Redo")); - - m_undo_widget = undo_button_widget.get(); - m_redo_widget = redo_button_widget.get(); - - m_widgets.insert(m_widgets.begin(), std::move(undo_button_widget)); - m_widgets.insert(m_widgets.begin() + 1, std::move(redo_button_widget)); - } - else if (!g_config->editor_undo_tracking && m_undo_widget) - { - // Remove undo/redo button widgets. - m_widgets.erase(std::remove_if( - m_widgets.begin(), m_widgets.end(), - [this](const std::unique_ptr& widget) { - const Widget* ptr = widget.get(); - return ptr == m_undo_widget || ptr == m_redo_widget; - }), m_widgets.end()); - m_undo_widget = nullptr; - m_redo_widget = nullptr; - } - + m_undo_widget->set_disabled(true); + m_redo_widget->set_disabled(true); // Toggle undo tracking for all sectors. for (const auto& sector : m_level->m_sectors) sector->toggle_undo_tracking(g_config->editor_undo_tracking); @@ -1504,6 +1491,20 @@ Editor::redo() m_layers_widget->update_current_tip(); } +void +Editor::set_undo_disabled(bool state) +{ + if (m_undo_widget) + m_undo_widget->set_disabled(state); +} + +void +Editor::set_redo_disabled(bool state) +{ + if (m_redo_widget) + m_redo_widget->set_disabled(state); +} + IntegrationStatus Editor::get_status() const { diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 9fed81a4e5..a22f5aea1c 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -187,6 +187,8 @@ class Editor final : public Screen, void undo(); void redo(); + void set_undo_disabled(bool state); + void set_redo_disabled(bool state); void pack_addon(); inline void on_exit(exit_cb_t exit_cb) { m_on_exit_cb = exit_cb; } diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index ea7199431f..457dbf9ed3 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -330,6 +330,7 @@ GameObjectManager::flush_game_objects() m_undo_stack.emplace_back(m_change_uid_generator.next(), std::move(m_pending_change_stack)); m_redo_stack.clear(); undo_stack_cleanup(); + update_editor_buttons(); } m_initialized = true; @@ -413,6 +414,7 @@ GameObjectManager::undo_stack_cleanup() if (current_size > m_undo_stack_size) m_undo_stack.erase(m_undo_stack.begin(), m_undo_stack.begin() + (current_size - m_undo_stack_size)); + update_editor_buttons(); } void @@ -509,6 +511,8 @@ GameObjectManager::undo() m_redo_stack.push_back(std::move(change_set)); } m_undo_stack.pop_back(); + + update_editor_buttons(); } void @@ -538,6 +542,18 @@ GameObjectManager::redo() m_undo_stack.push_back(std::move(change_set)); } m_redo_stack.pop_back(); + + update_editor_buttons(); +} + +void +GameObjectManager::update_editor_buttons() +{ + if (Editor::current()) + { + Editor::current()->set_undo_disabled(m_undo_stack.empty()); + Editor::current()->set_redo_disabled(m_redo_stack.empty()); + } } void @@ -656,6 +672,7 @@ GameObjectManager::clear_undo_stack() m_undo_stack.clear(); m_redo_stack.clear(); m_last_saved_change = UID(); + update_editor_buttons(); } bool diff --git a/src/supertux/game_object_manager.hpp b/src/supertux/game_object_manager.hpp index afa4d150f3..391e6f3eca 100644 --- a/src/supertux/game_object_manager.hpp +++ b/src/supertux/game_object_manager.hpp @@ -334,6 +334,8 @@ class GameObjectManager : public ExposableClass void this_before_object_add(GameObject& object); void this_before_object_remove(GameObject& object); + void update_editor_buttons(); + protected: /** An initial flush_game_objects() call has been initiated. */ bool m_initialized; From 085b0072e4106726be7b90255e477bdd56dbcee2 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 03:08:36 -0400 Subject: [PATCH 100/141] Fix undo buttons always being pressed --- src/editor/button_widget.cpp | 6 ++++-- src/supertux/game_object_manager.cpp | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index 3c26e94def..7f7d538edf 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -56,7 +56,6 @@ ButtonWidget::draw(DrawingContext& context) context.color().draw_filled_rect(m_rect, Color(0.0f, 0.0f, 0.0f, 0.6f), 4.0f, LAYER_GUI-5); - std::cout << m_disabled << std::endl; if (m_sprite) { if (m_disabled) context.set_alpha(0.4); m_sprite->draw_scaled(context.color(), m_rect.grown(-4.0f), LAYER_GUI - 5); @@ -94,7 +93,8 @@ bool ButtonWidget::on_mouse_button_up(const SDL_MouseButtonEvent& button) { if (button.button != SDL_BUTTON_LEFT) return false; - if (m_disabled) return true; + // XXX: these shouldn't pass through + if (m_disabled) return false; m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); @@ -116,6 +116,8 @@ bool ButtonWidget::on_mouse_button_down(const SDL_MouseButtonEvent& button) { if (button.button != SDL_BUTTON_LEFT || m_disabled) return false; + // XXX: these shouldn't pass through + if (m_disabled) return false; m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y); diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index 457dbf9ed3..7922f90561 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -414,7 +414,6 @@ GameObjectManager::undo_stack_cleanup() if (current_size > m_undo_stack_size) m_undo_stack.erase(m_undo_stack.begin(), m_undo_stack.begin() + (current_size - m_undo_stack_size)); - update_editor_buttons(); } void @@ -672,7 +671,6 @@ GameObjectManager::clear_undo_stack() m_undo_stack.clear(); m_redo_stack.clear(); m_last_saved_change = UID(); - update_editor_buttons(); } bool From 63a7bb3129afbd79e1dcf07b2197d7ede442bc21 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 03:17:19 -0400 Subject: [PATCH 101/141] Ctrl+alt+shift+T to test at last tested position --- src/editor/editor.cpp | 18 +++++++++++++++--- src/editor/editor.hpp | 2 ++ src/supertux/menu/editor_menu.cpp | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 3da3800f11..60d8c49e39 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -137,6 +137,7 @@ Editor::Editor() : m_particle_editor_filename(), m_ctrl_pressed(false), m_shift_pressed(false), + m_alt_pressed(false), m_sector(), m_levelloaded(false), m_leveltested(false), @@ -161,7 +162,8 @@ Editor::Editor() : m_layers_widget_needs_refresh(false), m_script_manager(), m_on_exit_cb(nullptr), - m_save_temp_level(false) + m_save_temp_level(false), + m_last_test_pos(std::nullopt) { auto toolbox_widget = std::make_unique(*this); auto layers_widget = std::make_unique(*this); @@ -1232,6 +1234,7 @@ Editor::event(const SDL_Event& ev) { m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; + m_alt_pressed = ev.key.keysym.mod & KMOD_ALT; if (m_ctrl_pressed) m_scroll_speed = 16.0f; @@ -1248,10 +1251,18 @@ Editor::event(const SDL_Event& ev) switch (ev.key.keysym.sym) { case SDLK_t: + if (m_shift_pressed && m_alt_pressed) + { + test_level(m_last_test_pos); + break; + } + if (m_shift_pressed) - test_level(std::pair(get_sector()->get_name(), m_overlay_widget->get_sector_pos())); + m_last_test_pos = std::pair(get_sector()->get_name(), m_overlay_widget->get_sector_pos()); else - test_level(std::nullopt); + m_last_test_pos = std::nullopt; + + test_level(m_last_test_pos); break; case SDLK_s: save_level(); @@ -1283,6 +1294,7 @@ Editor::event(const SDL_Event& ev) { m_ctrl_pressed = ev.key.keysym.mod & KMOD_CTRL; m_shift_pressed = ev.key.keysym.mod & KMOD_SHIFT; + m_alt_pressed = ev.key.keysym.mod & KMOD_ALT; if (!m_ctrl_pressed && !(ev.key.keysym.mod & KMOD_RSHIFT)) m_scroll_speed = 32.0f; diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index a22f5aea1c..bc570b64c0 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -238,6 +238,7 @@ class Editor final : public Screen, bool m_ctrl_pressed; bool m_shift_pressed; + bool m_alt_pressed; ScriptManager m_script_manager; @@ -254,6 +255,7 @@ class Editor final : public Screen, bool m_has_deprecated_tiles; bool m_temp_level; + std::optional> m_last_test_pos; std::vector > m_widgets; std::vector> m_controls; diff --git a/src/supertux/menu/editor_menu.cpp b/src/supertux/menu/editor_menu.cpp index a561813844..e988babbf3 100644 --- a/src/supertux/menu/editor_menu.cpp +++ b/src/supertux/menu/editor_menu.cpp @@ -180,6 +180,7 @@ EditorMenu::menu_action(MenuItem& item) "Ctrl+S = " + _("Save") + "\n" + "Ctrl+T = " + _("Test") + "\n" + "Ctrl+Shift+T = " + _("Test at Cursor") + "\n" + + "Ctrl+Alt+Shift+T = " + _("Test at Last Position") + "\n" + "Ctrl+Z = " + _("Undo") + "\n" + "Ctrl+Y = " + _("Redo") + "\n" + "F5 = " + _("Toggle Autotiling") + "\n" + From ddae3ea439e11c738a66886e55c4bbea157fbe08 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 03:19:25 -0400 Subject: [PATCH 102/141] Also store literal spawnpoint test as last pos --- src/editor/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 60d8c49e39..86f39f7769 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -538,6 +538,7 @@ Editor::update(float dt_sec, const Controller& controller) if (m_test_request) { m_test_request = false; MouseCursor::current()->set_icon(nullptr); + m_last_test_pos = m_test_pos; test_level(m_test_pos); return; } From 0b2eb0a7ec543e73f1076cdd43f33c5bed0de280 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sat, 23 Aug 2025 03:20:45 -0400 Subject: [PATCH 103/141] Don't show empty name in GameMenu --- src/supertux/menu/game_menu.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index 3077beda76..bafb8748e7 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -53,8 +53,12 @@ GameMenu::GameMenu() : { Level& level = GameSession::current()->get_current_level(); - add_label(level.m_name); - add_hl(); + if (!level.get_name().empty()) + { + add_label(level.get_name()); + add_hl(); + } + add_entry(MNID_CONTINUE, _("Continue")); add_entry(MNID_RESETLEVEL, _("Restart Level")); From ba2c01aef4fb8d1b49183a8ba7638eaee5b46f92 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Sat, 23 Aug 2025 15:19:50 -0400 Subject: [PATCH 104/141] No shift for grid shapping --- src/editor/overlay_widget.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index fe6cd1da44..1d30e18ef8 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -1149,11 +1149,7 @@ EditorOverlayWidget::on_key_up(const SDL_KeyboardEvent& key) { std::uint16_t mod = key.keysym.mod; - if (mod & KMOD_SHIFT) - { - g_config->editor_snap_to_grid = !g_config->editor_snap_to_grid; - } - else if (!m_editor.m_ctrl_pressed) + if (!m_editor.m_ctrl_pressed) { m_autotile_mode = g_config->editor_autotile_mode; @@ -1177,7 +1173,7 @@ EditorOverlayWidget::on_key_down(const SDL_KeyboardEvent& key) { g_config->editor_render_grid = !g_config->editor_render_grid; } - else if (sym == SDLK_F7 || mod & KMOD_SHIFT) + else if (sym == SDLK_F7) { g_config->editor_snap_to_grid = !g_config->editor_snap_to_grid; } From 21c476d59b42cc7250903c7f29beb40044d53d2c Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sun, 24 Aug 2025 03:01:59 -0400 Subject: [PATCH 105/141] Industrial autotile stuff --- data/images/autotiles.satc | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/data/images/autotiles.satc b/data/images/autotiles.satc index 5e104f1ec1..d5c8b93e39 100644 --- a/data/images/autotiles.satc +++ b/data/images/autotiles.satc @@ -10849,6 +10849,56 @@ ) + + (autotileset + (name "industrial_horiz") + (default 3170) + (autotile + (id 3183) + (solid #t) + (mask "***01***") + ) + (autotile + (id 4623) + (solid #t) + (mask "***11***") + ) + (autotile + (id 3184) + (solid #t) + (mask "***10***") + ) + (autotile + (id 3170) + (solid #t) + (mask "***00***") + ) + ) + + (autotileset + (name "industrial_vert") + (default 3170) + (autotile + (id 3189) + (solid #t) + (mask "*0****1*") + ) + (autotile + (id 4627) + (solid #t) + (mask "*1****1*") + ) + (autotile + (id 3190) + (solid #t) + (mask "*1****0*") + ) + (autotile + (id 3170) + (solid #t) + (mask "*0****0*") + ) + ) ;; SUPPORT MULTIPLE AUTOTILESETS PER TILE - END ) From f9a6c4023282fc4faddcfa59715b74b30febed11 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Sun, 24 Aug 2025 19:16:35 -0400 Subject: [PATCH 106/141] Improve tile placing performance This seems to cause the autotile code to constantly run even if nothing has been placed. --- src/editor/editor.hpp | 2 ++ src/editor/overlay_widget.cpp | 11 +++++++++++ src/editor/overlay_widget.hpp | 1 + src/editor/tilebox.cpp | 1 + 4 files changed, 15 insertions(+) diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index bc570b64c0..1e981c0d3d 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -243,6 +243,8 @@ class Editor final : public Screen, ScriptManager m_script_manager; exit_cb_t m_on_exit_cb; + + bool m_tilebox_something_selected; private: Sector* m_sector; diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index 1d30e18ef8..b69ee7ebd0 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -68,6 +68,7 @@ EditorOverlayWidget::EditorOverlayWidget(Editor& editor) : m_hovered_tile(0, 0), m_hovered_tile_prev(0, 0), m_last_hovered_tile(0, 0), + m_last_target_pos(0, 0), m_sector_pos(0, 0), m_mouse_pos(0, 0), m_previous_mouse_pos(0, 0), @@ -206,6 +207,13 @@ EditorOverlayWidget::put_tiles(const Vector& target_tile, TileSelection* tiles) { m_editor.get_selected_tilemap()->save_state(); + // Don't put tile if the position (or tile) hasn't changed + if (floor(m_last_target_pos.x) == floor(target_tile.x) && + floor(m_last_target_pos.y) == floor(target_tile.y) && + Editor::current()->m_tilebox_something_selected == false) + { + return; + } Vector add_tile(0.0f, 0.0f); for (add_tile.x = static_cast(tiles->m_width) - 1.0f; add_tile.x >= 0.0f; add_tile.x--) { @@ -229,6 +237,9 @@ EditorOverlayWidget::put_tiles(const Vector& target_tile, TileSelection* tiles) input_tile(target_tile + add_tile, tile); } // for tile y } // for tile x + + m_last_target_pos = target_tile; + Editor::current()->m_tilebox_something_selected = false; } namespace { diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index d5bde5f0f5..94b0c0b397 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -143,6 +143,7 @@ class EditorOverlayWidget final : public Widget Vector m_sector_pos; Vector m_mouse_pos; Vector m_previous_mouse_pos; + Vector m_last_target_pos; std::chrono::steady_clock::time_point m_time_prev_put_tile; diff --git a/src/editor/tilebox.cpp b/src/editor/tilebox.cpp index aa2b101fc1..6f1faeeb47 100644 --- a/src/editor/tilebox.cpp +++ b/src/editor/tilebox.cpp @@ -162,6 +162,7 @@ EditorTilebox::selection_draw_rect() const if (select.get_top() < m_rect.get_top()) // Do not go outside toolbox select.set_top(m_rect.get_top()); + Editor::current()->m_tilebox_something_selected = true; return select; } From cc5189012cbc78ca56d7aeac331a21526ad4b296 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Mon, 25 Aug 2025 02:16:12 -0400 Subject: [PATCH 107/141] Use UID for external scripts; squirrel get_uid method Not sure why I didn't do this earlier on; the UID is unique and will prevent file clashes --- src/badguy/badguy.cpp | 2 +- src/badguy/boss.cpp | 2 +- src/badguy/crusher.cpp | 2 +- src/badguy/granito.cpp | 4 ++-- src/badguy/granito_big.cpp | 2 +- src/editor/editor_comment.cpp | 2 +- src/editor/node_marker.cpp | 2 +- src/editor/object_option.cpp | 22 ++++++++++++---------- src/editor/object_option.hpp | 8 ++++++-- src/editor/object_settings.cpp | 19 +++++++++++-------- src/editor/object_settings.hpp | 9 +++++---- src/gui/item_script.cpp | 7 ++++--- src/gui/item_script.hpp | 3 ++- src/gui/menu.cpp | 4 ++-- src/gui/menu.hpp | 3 ++- src/gui/menu_script.cpp | 12 ++++++------ src/gui/menu_script.hpp | 3 ++- src/object/bonus_block.cpp | 2 +- src/object/coin.cpp | 4 ++-- src/object/infoblock.cpp | 2 +- src/object/ispy.cpp | 2 +- src/object/powerup.cpp | 2 +- src/object/pushbutton.cpp | 2 +- src/object/rock.cpp | 4 ++-- src/object/scripted_object.cpp | 2 +- src/object/textscroller.cpp | 2 +- src/supertux/game_object.cpp | 3 ++- src/supertux/game_object.hpp | 3 +++ src/supertux/menu/editor_sector_menu.cpp | 2 +- src/trigger/door.cpp | 2 +- src/trigger/scripttrigger.cpp | 2 +- src/trigger/secretarea_trigger.cpp | 2 +- src/trigger/switch.cpp | 4 ++-- src/util/script_manager.cpp | 20 ++++++++++---------- src/util/script_manager.hpp | 16 ++++++++-------- src/util/uid.hpp | 2 ++ src/worldmap/level_tile.cpp | 2 +- src/worldmap/special_tile.cpp | 2 +- 38 files changed, 104 insertions(+), 84 deletions(-) diff --git a/src/badguy/badguy.cpp b/src/badguy/badguy.cpp index 6a82c21486..4f43165668 100644 --- a/src/badguy/badguy.cpp +++ b/src/badguy/badguy.cpp @@ -1268,7 +1268,7 @@ BadGuy::get_settings() if (!get_allowed_directions().empty()) result.add_direction(_("Direction"), &m_start_dir, get_allowed_directions(), "direction"); - result.add_script(_("Death script"), &m_dead_script, "dead-script"); + result.add_script(get_uid(), _("Death script"), &m_dead_script, "dead-script"); result.reorder({"direction", "sprite", "x", "y"}); diff --git a/src/badguy/boss.cpp b/src/badguy/boss.cpp index 0de39d3a91..3ac81227a1 100644 --- a/src/badguy/boss.cpp +++ b/src/badguy/boss.cpp @@ -103,7 +103,7 @@ Boss::get_settings() /* l10n: Pinch Mode refers to a particular boss mode that gets activated once the boss has lost the specified amounts of lives. This setting specifies the squirrel script that gets run to activate boss mode. */ - result.add_script(_("Pinch Mode Activation Script"), &m_pinch_activation_script, "pinch-activation-script"); + result.add_script(get_uid(), _("Pinch Mode Activation Script"), &m_pinch_activation_script, "pinch-activation-script"); return result; } diff --git a/src/badguy/crusher.cpp b/src/badguy/crusher.cpp index 59792f718b..2023316c20 100644 --- a/src/badguy/crusher.cpp +++ b/src/badguy/crusher.cpp @@ -1229,7 +1229,7 @@ Crusher::get_settings() static_cast(CrusherDirection::DOWN), "direction"); - result.add_script(_("Crush script"), &m_crush_script, "crush-script"); + result.add_script(get_uid(), _("Crush script"), &m_crush_script, "crush-script"); result.reorder({ "direction", "sprite", "crush-script", "x", "y" }); return result; diff --git a/src/badguy/granito.cpp b/src/badguy/granito.cpp index eba26b5814..755b73ffed 100644 --- a/src/badguy/granito.cpp +++ b/src/badguy/granito.cpp @@ -279,8 +279,8 @@ Granito::get_settings() settings.remove("dead-script"); - settings.add_script(_("Detect script"), &m_detect_script, "detect-script"); - settings.add_script(_("Carried script"), &m_carried_script, "carried-script"); + settings.add_script(get_uid(), _("Detect script"), &m_detect_script, "detect-script"); + settings.add_script(get_uid(), _("Carried script"), &m_carried_script, "carried-script"); return settings; } diff --git a/src/badguy/granito_big.cpp b/src/badguy/granito_big.cpp index 2509fd25f7..6935d5e82c 100644 --- a/src/badguy/granito_big.cpp +++ b/src/badguy/granito_big.cpp @@ -62,7 +62,7 @@ GranitoBig::get_settings() // No need to make another member for the carrying script. // Just repurpose the carried script. - settings.add_script(_("Carrying Script"), &m_carried_script, "carrying-script"); + settings.add_script(get_uid(), _("Carrying Script"), &m_carried_script, "carrying-script"); return settings; } diff --git a/src/editor/editor_comment.cpp b/src/editor/editor_comment.cpp index eed18d9fd4..19c1855965 100644 --- a/src/editor/editor_comment.cpp +++ b/src/editor/editor_comment.cpp @@ -94,7 +94,7 @@ EditorComment::get_settings() { ObjectSettings result = MovingObject::get_settings(); - result.add_multiline_text(_("Comment"), &m_comment, "comment"); + result.add_multiline_text(get_uid(), _("Comment"), &m_comment, "comment"); return result; } diff --git a/src/editor/node_marker.cpp b/src/editor/node_marker.cpp index a55fa2f349..16eb537b62 100644 --- a/src/editor/node_marker.cpp +++ b/src/editor/node_marker.cpp @@ -119,7 +119,7 @@ NodeMarker::editor_delete() ObjectSettings NodeMarker::get_settings() { - ObjectSettings result(_("Path Node")); + ObjectSettings result(_("Path Node"), get_uid()); result.add_label(_("Press CTRL to move Bézier handles")); result.add_float(_("Time"), &(m_node->time)); result.add_float(_("Speed"), &(m_node->speed)); diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 21cdb4f206..ca0bb535cd 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -352,11 +352,12 @@ StringObjectOption::create_interface_control() const return textbox; } -StringMultilineObjectOption::StringMultilineObjectOption(const std::string& text, std::string* pointer, const std::string& key, +StringMultilineObjectOption::StringMultilineObjectOption(UID uid, const std::string& text, std::string* pointer, const std::string& key, std::optional default_value, unsigned int flags) : ObjectOption(text, key, flags, pointer), - m_default_value(std::move(default_value)) + m_default_value(std::move(default_value)), + m_uid(uid) { } @@ -392,15 +393,15 @@ StringMultilineObjectOption::to_string() const void StringMultilineObjectOption::add_to_menu(Menu& menu) const { - menu.add_script(m_key, get_text(), m_value_pointer); + menu.add_script(m_uid, m_key, get_text(), m_value_pointer); } std::unique_ptr StringMultilineObjectOption::create_interface_control() const { auto button = std::make_unique(_("Edit...")); - button->m_on_activate_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { - MenuManager::instance().push_menu(std::make_unique(key, value_ptr)); + button->m_on_activate_callbacks.emplace_back([uid = m_uid, key = m_key, value_ptr = m_value_pointer]() { + MenuManager::instance().push_menu(std::make_unique(uid, key, value_ptr)); }); button->set_rect(Rectf(0, 32, 20, 32)); return button; @@ -544,9 +545,10 @@ EnumObjectOption::create_interface_control() const return dropdown; } -ScriptObjectOption::ScriptObjectOption(const std::string& text, std::string* pointer, const std::string& key, +ScriptObjectOption::ScriptObjectOption(UID uid, const std::string& text, std::string* pointer, const std::string& key, unsigned int flags) : - ObjectOption(text, key, flags, pointer) + ObjectOption(text, key, flags, pointer), + m_uid(uid) { } @@ -580,15 +582,15 @@ ScriptObjectOption::to_string() const void ScriptObjectOption::add_to_menu(Menu& menu) const { - menu.add_script(m_key, get_text(), m_value_pointer); + menu.add_script(m_uid, m_key, get_text(), m_value_pointer); } std::unique_ptr ScriptObjectOption::create_interface_control() const { auto button = std::make_unique(_("Edit...")); - button->m_on_activate_callbacks.emplace_back([key = m_key, value_ptr = m_value_pointer]() { - MenuManager::instance().push_menu(std::make_unique(key, value_ptr)); + button->m_on_activate_callbacks.emplace_back([uid = m_uid, key = m_key, value_ptr = m_value_pointer]() { + MenuManager::instance().push_menu(std::make_unique(uid, key, value_ptr)); }); button->set_rect(Rectf(0, 32, 20, 32)); return button; diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index b69077cb11..41efbceca6 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -220,7 +220,7 @@ class StringObjectOption final : public ObjectOption class StringMultilineObjectOption final : public ObjectOption { public: - StringMultilineObjectOption(const std::string& text, std::string* pointer, const std::string& key, + StringMultilineObjectOption(UID uid, const std::string& text, std::string* pointer, const std::string& key, std::optional default_value, unsigned int flags); @@ -232,6 +232,7 @@ class StringMultilineObjectOption final : public ObjectOption private: std::optional m_default_value; + UID m_uid; private: StringMultilineObjectOption(const StringMultilineObjectOption&) = delete; @@ -292,7 +293,7 @@ class EnumObjectOption final : public ObjectOption class ScriptObjectOption final : public ObjectOption { public: - ScriptObjectOption(const std::string& text, std::string* pointer, const std::string& key, + ScriptObjectOption(UID uid, const std::string& text, std::string* pointer, const std::string& key, unsigned int flags); virtual void parse(const ReaderMapping& reader) override; @@ -300,6 +301,9 @@ class ScriptObjectOption final : public ObjectOption virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; virtual std::unique_ptr create_interface_control() const override; + +private: + UID m_uid; private: ScriptObjectOption(const ScriptObjectOption&) = delete; diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index a1771127b5..c994c3697c 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -25,20 +25,23 @@ #include "util/log.hpp" #include "video/color.hpp" -ObjectSettings::ObjectSettings(const std::string& name) : - m_name(name), +ObjectSettings::ObjectSettings(std::string name, UID uid) : + m_name(std::move(name)), + m_uid(std::move(uid)), m_options() { } ObjectSettings::ObjectSettings(ObjectSettings&& other) : m_name(other.m_name), + m_uid(other.m_uid), m_options(std::move(other.m_options)) { } ObjectSettings::ObjectSettings(ObjectSettings* obj) : m_name(obj->m_name), + m_uid(obj->m_uid), m_options() { // for (auto &option : obj->m_options) @@ -185,10 +188,10 @@ ObjectSettings::add_remove() } void -ObjectSettings::add_script(const std::string& text, std::string* value_ptr, +ObjectSettings::add_script(UID uid, const std::string& text, std::string* value_ptr, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, flags)); + add_option(std::make_unique(uid, text, value_ptr, key, flags)); } void @@ -211,21 +214,21 @@ ObjectSettings::add_translatable_text(const std::string& text, std::string* valu } void -ObjectSettings::add_multiline_text(const std::string& text, std::string* value_ptr, +ObjectSettings::add_multiline_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, flags)); + add_option(std::make_unique(uid, text, value_ptr, key, default_value, flags)); } void -ObjectSettings::add_multiline_translatable_text(const std::string& text, std::string* value_ptr, +ObjectSettings::add_multiline_translatable_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, + add_option(std::make_unique(uid, text, value_ptr, key, default_value, flags | OPTION_TRANSLATABLE)); } diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index 1be6b8efa6..be566fc305 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -43,7 +43,7 @@ class Value; class ObjectSettings final { public: - ObjectSettings(const std::string& name); + ObjectSettings(std::string name, UID uid); ObjectSettings(ObjectSettings&& other); ObjectSettings(ObjectSettings* obj); @@ -89,7 +89,7 @@ class ObjectSettings final const std::optional& default_value = {}, unsigned int flags = 0); void add_remove(); - void add_script(const std::string& text, std::string* value_ptr, + void add_script(UID uid, const std::string& text, std::string* value_ptr, const std::string& key = {}, unsigned int flags = 0); void add_text(const std::string& text, std::string* value_ptr, const std::string& key = {}, @@ -99,11 +99,11 @@ class ObjectSettings final const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_multiline_text(const std::string& text, std::string* value_ptr, + void add_multiline_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_multiline_translatable_text(const std::string& text, std::string* value_ptr, + void add_multiline_translatable_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); @@ -189,6 +189,7 @@ class ObjectSettings final private: std::string m_name; + UID m_uid; std::vector > m_options; private: diff --git a/src/gui/item_script.cpp b/src/gui/item_script.cpp index 8deedaaacd..94e4cceaa5 100644 --- a/src/gui/item_script.cpp +++ b/src/gui/item_script.cpp @@ -20,16 +20,17 @@ #include "gui/menu_manager.hpp" #include "gui/menu_script.hpp" -ItemScript::ItemScript(const std::string& key, const std::string& text, std::string* script_, int id) : +ItemScript::ItemScript(UID uid, const std::string& key, const std::string& text, std::string* script_, int id) : MenuItem(text, id), script(script_), - m_key(std::move(key)) + m_key(std::move(key)), + m_uid(std::move(uid)) { } void ItemScript::process_action(const MenuAction& action) { if (action == MenuAction::HIT) { - MenuManager::instance().push_menu(std::make_unique(m_key, script)); + MenuManager::instance().push_menu(std::make_unique(m_uid, m_key, script)); } } diff --git a/src/gui/item_script.hpp b/src/gui/item_script.hpp index 4b641d14aa..3add430096 100644 --- a/src/gui/item_script.hpp +++ b/src/gui/item_script.hpp @@ -21,7 +21,7 @@ class ItemScript final : public MenuItem { public: - ItemScript(const std::string& key, const std::string& text_, std::string* script_, int id = -1); + ItemScript(UID uid, const std::string& key, const std::string& text_, std::string* script_, int id = -1); /** Processes the menu action. */ virtual void process_action(const MenuAction& action) override; @@ -29,6 +29,7 @@ class ItemScript final : public MenuItem private: std::string* script; std::string m_key; + UID m_uid; private: ItemScript(const ItemScript&) = delete; diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index 16c6803967..35a605848e 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -167,9 +167,9 @@ Menu::add_textfield(const std::string& text, std::string* input, int id) } ItemScript& -Menu::add_script(const std::string& key, const std::string& text, std::string* script, int id) +Menu::add_script(UID uid, const std::string& key, const std::string& text, std::string* script, int id) { - return add_item(key, text, script, id); + return add_item(uid, key, text, script, id); } ItemIntField& diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index 5b7cb9f73d..23e2cf677f 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -22,6 +22,7 @@ #include "gui/menu_action.hpp" #include "math/vector.hpp" +#include "util/uid.hpp" #include "video/color.hpp" #include "video/drawing_context.hpp" @@ -90,7 +91,7 @@ class Menu ItemStringSelect& add_string_select(int id, const std::string& text, int* selected, const std::vector& strings); ItemStringSelect& add_string_select(int id, const std::string& text, int default_item, const std::vector& strings); ItemTextField& add_textfield(const std::string& text, std::string* input, int id = -1); - ItemScript& add_script(const std::string& key, const std::string& text, std::string* script, int id = -1); + ItemScript& add_script(UID uid, const std::string& key, const std::string& text, std::string* script, int id = -1); ItemIntField& add_intfield(const std::string& text, int* input, int id = -1, bool positive = false); ItemFloatField& add_floatfield(const std::string& text, float* input, int id = -1, bool positive = false); ItemAction& add_file(const std::string& text, std::string* input, const std::vector& extensions, diff --git a/src/gui/menu_script.cpp b/src/gui/menu_script.cpp index 5478a0d19b..bdaabb9f33 100644 --- a/src/gui/menu_script.cpp +++ b/src/gui/menu_script.cpp @@ -20,11 +20,12 @@ #include "gui/item_script_line.hpp" #include "util/gettext.hpp" -ScriptMenu::ScriptMenu(const std::string& key, std::string* script_) : +ScriptMenu::ScriptMenu(UID uid, const std::string& key, std::string* script_) : base_script(script_), script_strings(), m_start_time(time(0)), - m_key(key) + m_key(key), + m_uid(uid) { script_strings.clear(); @@ -46,19 +47,18 @@ ScriptMenu::ScriptMenu(const std::string& key, std::string* script_) : //add_script_line(base_script); if (Editor::current()) - Editor::current()->m_script_manager.register_script(m_key, base_script); + Editor::current()->m_script_manager.register_script(m_uid, base_script); add_hl(); add_entry(_("Open in editor"), [this]{ - FileSystem::open_editor(ScriptManager::full_filename_from_key(m_key)); + FileSystem::open_editor(ScriptManager::full_filename_from_key(m_uid)); }); add_back(_("OK")); } ScriptMenu::~ScriptMenu() { - time_t mtime = Editor::current()->m_script_manager.get_mtime( - ScriptManager::full_filename_from_key(m_key)); + time_t mtime = Editor::current()->m_script_manager.get_mtime(m_uid); // Don't save if the external file was edited. if (mtime > m_start_time) diff --git a/src/gui/menu_script.hpp b/src/gui/menu_script.hpp index 41bf40602e..fc9274716e 100644 --- a/src/gui/menu_script.hpp +++ b/src/gui/menu_script.hpp @@ -24,7 +24,7 @@ class ItemScriptLine; class ScriptMenu final : public Menu { public: - ScriptMenu(const std::string& key, std::string* script_); + ScriptMenu(UID uid, const std::string& key, std::string* script_); ~ScriptMenu() override; void menu_action(MenuItem& item) override; @@ -40,6 +40,7 @@ class ScriptMenu final : public Menu std::string* base_script; std::vector > script_strings; std::string m_key; + UID m_uid; void push_string(const std::string& new_line); diff --git a/src/object/bonus_block.cpp b/src/object/bonus_block.cpp index 273a6f594e..ca722bc47d 100644 --- a/src/object/bonus_block.cpp +++ b/src/object/bonus_block.cpp @@ -261,7 +261,7 @@ BonusBlock::get_settings() { ObjectSettings result = Block::get_settings(); - result.add_script(_("Script"), &m_script, "script"); + result.add_script(get_uid(), _("Script"), &m_script, "script"); result.add_int(_("Count"), &m_hit_counter, "count", get_default_hit_counter()); result.add_enum(_("Content"), reinterpret_cast(&m_contents), { _("Coin"), _("Growth (fire flower)"), _("Growth (ice flower)"), _("Growth (air flower)"), diff --git a/src/object/coin.cpp b/src/object/coin.cpp index 16657398a0..8c7db8bea4 100644 --- a/src/object/coin.cpp +++ b/src/object/coin.cpp @@ -335,7 +335,7 @@ Coin::get_settings() result.add_path_handle(_("Handle"), m_path_handle, "handle"); } - result.add_script(_("Collect script"), &m_collect_script, "collect-script"); + result.add_script(get_uid(), _("Collect script"), &m_collect_script, "collect-script"); result.reorder({"collect-script", "path-ref"}); @@ -371,7 +371,7 @@ HeavyCoin::get_settings() { auto result = MovingSprite::get_settings(); - result.add_script(_("Collect script"), &m_collect_script, "collect-script"); + result.add_script(get_uid(), _("Collect script"), &m_collect_script, "collect-script"); result.reorder({"collect-script", "sprite", "x", "y"}); diff --git a/src/object/infoblock.cpp b/src/object/infoblock.cpp index b2a0520503..4e1f8699c4 100644 --- a/src/object/infoblock.cpp +++ b/src/object/infoblock.cpp @@ -73,7 +73,7 @@ InfoBlock::get_settings() { ObjectSettings result = Block::get_settings(); - result.add_multiline_translatable_text(_("Message"), &m_message, "message"); + result.add_multiline_translatable_text(get_uid(), _("Message"), &m_message, "message"); result.add_color(_("Front Color"), &m_frontcolor, "frontcolor", Color(0.6f, 0.7f, 0.8f, 0.5f)); diff --git a/src/object/ispy.cpp b/src/object/ispy.cpp index 54f750843f..96eab771fd 100644 --- a/src/object/ispy.cpp +++ b/src/object/ispy.cpp @@ -52,7 +52,7 @@ Ispy::get_settings() { ObjectSettings result = StickyObject::get_settings(); - result.add_script(_("Script"), &m_script, "script"); + result.add_script(get_uid(), _("Script"), &m_script, "script"); result.add_direction(_("Direction"), &m_dir, { Direction::LEFT, Direction::RIGHT, Direction::UP, Direction::DOWN }, "direction"); diff --git a/src/object/powerup.cpp b/src/object/powerup.cpp index 54480880a5..5758a93a59 100644 --- a/src/object/powerup.cpp +++ b/src/object/powerup.cpp @@ -323,7 +323,7 @@ PowerUp::get_settings() { ObjectSettings result = MovingSprite::get_settings(); - result.add_script(_("Script"), &script, "script"); + result.add_script(get_uid(), _("Script"), &script, "script"); result.add_bool(_("Disable gravity"), &no_physics, "disable-physics", false); result.reorder({"script", "disable-physics", "sprite", "x", "y"}); diff --git a/src/object/pushbutton.cpp b/src/object/pushbutton.cpp index b24f92b65f..324a71db65 100644 --- a/src/object/pushbutton.cpp +++ b/src/object/pushbutton.cpp @@ -63,7 +63,7 @@ PushButton::get_settings() ObjectSettings result = StickyObject::get_settings(); result.add_direction(_("Direction"), &m_dir, { Direction::UP, Direction::DOWN }, "direction"); - result.add_script(_("Script"), &m_script, "script"); + result.add_script(get_uid(), _("Script"), &m_script, "script"); result.reorder({"direction", "script", "sticky", "x", "y"}); diff --git a/src/object/rock.cpp b/src/object/rock.cpp index 362315ca4c..1fae2d6451 100644 --- a/src/object/rock.cpp +++ b/src/object/rock.cpp @@ -313,8 +313,8 @@ ObjectSettings Rock::get_settings() { auto result = MovingSprite::get_settings(); - result.add_script(_("On-grab script"), &m_on_grab_script, "on-grab-script"); - result.add_script(_("On-ungrab script"), &m_on_ungrab_script, "on-ungrab-script"); + result.add_script(get_uid(), _("On-grab script"), &m_on_grab_script, "on-grab-script"); + result.add_script(get_uid(), _("On-ungrab script"), &m_on_ungrab_script, "on-ungrab-script"); return result; } diff --git a/src/object/scripted_object.cpp b/src/object/scripted_object.cpp index 35c5b2aec1..b6ab66d37f 100644 --- a/src/object/scripted_object.cpp +++ b/src/object/scripted_object.cpp @@ -72,7 +72,7 @@ ScriptedObject::get_settings() result.add_bool(_("Solid"), &solid, "solid", true); result.add_bool(_("Physics enabled"), &physic_enabled, "physic-enabled", true); result.add_bool(_("Visible"), &visible, "visible", true); - result.add_script(_("Hit script"), &hit_script, "hit-script"); + result.add_script(get_uid(), _("Hit script"), &hit_script, "hit-script"); result.reorder({"z-pos", "visible", "physic-enabled", "solid", "name", "sprite", "script", "button", "x", "y"}); diff --git a/src/object/textscroller.cpp b/src/object/textscroller.cpp index d9c9315cc9..e9a1118d62 100644 --- a/src/object/textscroller.cpp +++ b/src/object/textscroller.cpp @@ -341,7 +341,7 @@ TextScroller::get_settings() ObjectSettings result = GameObject::get_settings(); result.add_file(_("File"), &m_filename, "file"); - result.add_script(_("Finish Script"), &m_finish_script, "finish-script"); + result.add_script(get_uid(), _("Finish Script"), &m_finish_script, "finish-script"); result.add_float(_("Speed"), &m_default_speed, "speed", DEFAULT_SPEED); result.add_float(_("X-offset"), &m_x_offset, "x-offset"); result.add_bool(_("Controllable"), &m_controllable, "controllable", true); diff --git a/src/supertux/game_object.cpp b/src/supertux/game_object.cpp index 134087e7d1..e960910b25 100644 --- a/src/supertux/game_object.cpp +++ b/src/supertux/game_object.cpp @@ -130,7 +130,7 @@ GameObject::get_class_types() const ObjectSettings GameObject::get_settings() { - ObjectSettings result(get_display_name()); + ObjectSettings result(get_display_name(), get_uid()); result.add_int(_("Version"), &m_version, "version", 1, OPTION_HIDDEN); result.add_text(_("Name"), &m_name, "name", ""); @@ -298,6 +298,7 @@ GameObject::register_class(ssq::VM& vm) { ssq::Class cls = vm.addAbstractClass("GameObject"); + cls.addFunc("get_uid", &GameObject::get_uid_value); cls.addFunc("get_version", &GameObject::get_version); cls.addFunc("get_latest_version", &GameObject::get_latest_version); cls.addFunc("is_up_to_date", &GameObject::is_up_to_date); diff --git a/src/supertux/game_object.hpp b/src/supertux/game_object.hpp index 27e6b2a4cd..553de7070d 100644 --- a/src/supertux/game_object.hpp +++ b/src/supertux/game_object.hpp @@ -266,6 +266,9 @@ class GameObject : public ExposableClass int type_id_to_value(const std::string& id) const; std::string type_value_to_id(int value) const; +private: + inline uint32_t get_uid_value() { return m_uid.get_value(); } + private: inline void set_uid(const UID& uid) { m_uid = uid; } diff --git a/src/supertux/menu/editor_sector_menu.cpp b/src/supertux/menu/editor_sector_menu.cpp index 19fb1992c2..14890a28ac 100644 --- a/src/supertux/menu/editor_sector_menu.cpp +++ b/src/supertux/menu/editor_sector_menu.cpp @@ -34,7 +34,7 @@ EditorSectorMenu::EditorSectorMenu() : add_label(fmt::format(fmt::runtime(_("Sector {}")), sector->get_name())); add_hl(); add_textfield(_("Name"), §or->m_name); - add_script(sector->m_name, _("Initialization script"), §or->m_init_script); + add_script(UID(), sector->m_name, _("Initialization script"), §or->m_init_script); add_floatfield(_("Gravity"), §or->m_gravity); add_hl(); diff --git a/src/trigger/door.cpp b/src/trigger/door.cpp index 7a2b9c9461..525193a65f 100644 --- a/src/trigger/door.cpp +++ b/src/trigger/door.cpp @@ -76,7 +76,7 @@ Door::get_settings() { ObjectSettings result = SpritedTrigger::get_settings(); - result.add_script(_("Script"), &m_script, "script"); + result.add_script(get_uid(), _("Script"), &m_script, "script"); result.add_text(_("Sector"), &m_target_sector, "sector"); result.add_text(_("Spawn point"), &m_target_spawnpoint, "spawnpoint"); result.add_bool(_("Locked?"), &m_locked, "locked"); diff --git a/src/trigger/scripttrigger.cpp b/src/trigger/scripttrigger.cpp index d200d8a6be..3f15fa9f5a 100644 --- a/src/trigger/scripttrigger.cpp +++ b/src/trigger/scripttrigger.cpp @@ -48,7 +48,7 @@ ScriptTrigger::get_settings() { ObjectSettings result = Trigger::get_settings(); - result.add_script(_("Script"), &script, "script"); + result.add_script(get_uid(), _("Script"), &script, "script"); result.add_bool(_("Button"), &must_activate, "button"); result.add_bool(_("Oneshot"), &oneshot, "oneshot", false); diff --git a/src/trigger/secretarea_trigger.cpp b/src/trigger/secretarea_trigger.cpp index 847860737d..77ae34a5d2 100644 --- a/src/trigger/secretarea_trigger.cpp +++ b/src/trigger/secretarea_trigger.cpp @@ -55,7 +55,7 @@ SecretAreaTrigger::get_settings() result.add_text(_("Name"), &m_name); result.add_text(_("Fade tilemap"), &fade_tilemap, "fade-tilemap"); result.add_translatable_text(_("Message"), &message, "message"); - result.add_script(_("Script"), &script, "script"); + result.add_script(get_uid(), _("Script"), &script, "script"); result.reorder({"fade-tilemap", "script", "sprite", "message", "width", "height", "name", "x", "y"}); diff --git a/src/trigger/switch.cpp b/src/trigger/switch.cpp index 7976b7d656..6bb622483d 100644 --- a/src/trigger/switch.cpp +++ b/src/trigger/switch.cpp @@ -63,8 +63,8 @@ Switch::get_settings() result.add_direction(_("Direction"), &m_dir, { Direction::NONE, Direction::LEFT, Direction::RIGHT, Direction::UP, Direction::DOWN }, "direction"); - result.add_script(_("Turn on script"), &m_script, "script"); - result.add_script(_("Turn off script"), &m_off_script, "off-script"); + result.add_script(get_uid(), _("Turn on script"), &m_script, "script"); + result.add_script(get_uid(), _("Turn off script"), &m_off_script, "off-script"); result.reorder({"direction", "script", "off-script", "sticky", "sprite", "x", "y"}); diff --git a/src/util/script_manager.cpp b/src/util/script_manager.cpp index 02210390ed..8ed09bf0ca 100644 --- a/src/util/script_manager.cpp +++ b/src/util/script_manager.cpp @@ -30,32 +30,32 @@ ScriptManager::ScriptManager() : } bool -ScriptManager::is_script_registered(const std::string& key) +ScriptManager::is_script_registered(UID key) { auto res = std::find(m_scripts.begin(), m_scripts.end(), key); return res != m_scripts.end(); } std::string -ScriptManager::full_filename_from_key(const std::string& key) +ScriptManager::full_filename_from_key(UID key) { return FileSystem::join(PHYSFS_getWriteDir(), "tmp/" + filename_from_key(key)); } std::string -ScriptManager::filename_from_key(const std::string& key) +ScriptManager::filename_from_key(UID key) { - return key + "_" + std::to_string(std::hash()(key)).substr(0, 6) + ".nut"; + return "script_" + std::to_string(key.get_value()) + ".nut"; } time_t -ScriptManager::get_mtime(const std::string& key) +ScriptManager::get_mtime(UID key) { - return m_watcher.get_mtime(key); + return m_watcher.get_mtime(full_filename_from_key(key)); } void -ScriptManager::register_script(std::string key, std::string* script) +ScriptManager::register_script(UID key, std::string* script) { std::string full_filename = full_filename_from_key(key); @@ -68,10 +68,10 @@ ScriptManager::register_script(std::string key, std::string* script) if (is_script_registered(key)) return; - m_scripts.push_back({full_filename, script}); + m_scripts.push_back({key, script}); - m_watcher.start_monitoring(full_filename, [this](FileWatcher::FileInfo& file) { - auto res = std::find(m_scripts.begin(), m_scripts.end(), file.filename); + m_watcher.start_monitoring(full_filename, [this, key](FileWatcher::FileInfo& file) { + auto res = std::find(m_scripts.begin(), m_scripts.end(), key); if (res == m_scripts.end()) return; diff --git a/src/util/script_manager.hpp b/src/util/script_manager.hpp index 8ad2b15a57..9e95134450 100644 --- a/src/util/script_manager.hpp +++ b/src/util/script_manager.hpp @@ -17,9 +17,9 @@ #pragma once #include "util/file_watcher.hpp" +#include "util/uid.hpp" #include #include -#include #include class ScriptManager @@ -28,13 +28,13 @@ class ScriptManager using callback_t = std::function; struct ScriptInfo { - std::string key; + UID key; std::string* script; bool operator==(struct ScriptInfo& other) const { return other.key == key; } - bool operator==(const std::string& other) const { + bool operator==(const UID& other) const { return other == key; } }; @@ -42,13 +42,13 @@ class ScriptManager ScriptManager(); ~ScriptManager() = default; - static std::string filename_from_key(const std::string& key); - static std::string full_filename_from_key(const std::string& key); + static std::string filename_from_key(UID key); + static std::string full_filename_from_key(UID key); - time_t get_mtime(const std::string& key); + time_t get_mtime(UID key); - bool is_script_registered(const std::string& key); - void register_script(std::string key, std::string* script); + bool is_script_registered(UID key); + void register_script(UID key, std::string* script); void poll(); private: diff --git a/src/util/uid.hpp b/src/util/uid.hpp index a93f3ff259..8889bd35c8 100644 --- a/src/util/uid.hpp +++ b/src/util/uid.hpp @@ -74,6 +74,8 @@ class UID inline bool operator!=(const UID& other) const { return m_value != other.m_value; } + + inline uint32_t get_value() const { return m_value; } inline Magic get_magic() const { return static_cast((m_value & 0xffff0000u) >> 16); } diff --git a/src/worldmap/level_tile.cpp b/src/worldmap/level_tile.cpp index 1684238bbd..516368beab 100644 --- a/src/worldmap/level_tile.cpp +++ b/src/worldmap/level_tile.cpp @@ -151,7 +151,7 @@ LevelTile::get_settings() ObjectSettings result = WorldMapObject::get_settings(); result.add_level(_("Level"), &m_level_filename, "level", basedir); - result.add_script(_("Outro script"), &m_extro_script, "extro-script"); + result.add_script(get_uid(), _("Outro script"), &m_extro_script, "extro-script"); result.add_bool(_("Auto play"), &m_auto_play, "auto-play", false); result.add_color(_("Title colour"), &m_title_color, "color", Color::WHITE); diff --git a/src/worldmap/special_tile.cpp b/src/worldmap/special_tile.cpp index 8c309c7aaa..c4413254b0 100644 --- a/src/worldmap/special_tile.cpp +++ b/src/worldmap/special_tile.cpp @@ -73,7 +73,7 @@ SpecialTile::get_settings() result.add_translatable_text(_("Message"), &m_map_message, "map-message"); result.add_bool(_("Show message"), &m_passive_message, "passive-message", false); - result.add_script(_("Script"), &m_script, "script"); + result.add_script(get_uid(), _("Script"), &m_script, "script"); result.add_bool(_("Invisible"), &m_invisible, "invisible-tile", false); result.add_text(_("Direction"), &m_apply_direction, "apply-to-direction", "north-east-south-west"); From f18176d19a349e199ce4aba59e9e9a4ef92ac639 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Mon, 25 Aug 2025 02:36:44 -0400 Subject: [PATCH 108/141] Don't show big boss healthbar in editor --- src/badguy/boss.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/badguy/boss.cpp b/src/badguy/boss.cpp index 3ac81227a1..78144cdd1e 100644 --- a/src/badguy/boss.cpp +++ b/src/badguy/boss.cpp @@ -22,6 +22,7 @@ namespace #include "badguy/boss.hpp" +#include "editor/editor.hpp" #include "object/player.hpp" #include "sprite/sprite.hpp" #include "supertux/sector.hpp" @@ -67,7 +68,7 @@ Boss::draw(DrawingContext& context) void Boss::draw_hit_points(DrawingContext& context) { - if (m_hud_head) + if (m_hud_head && !Editor::is_active()) { context.push_transform(); context.set_translation(Vector(0, 0)); From 3644162182728897b2ac88d9389fe6307216a493 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Mon, 25 Aug 2025 02:43:49 -0400 Subject: [PATCH 109/141] Fix object grid drag feeling off (round, not floor) --- src/editor/overlay_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index b69ee7ebd0..7e13682104 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -723,7 +723,7 @@ EditorOverlayWidget::move_object() if (g_config->editor_snap_to_grid) { auto& snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; - new_pos = glm::floor(new_pos / static_cast(snap_grid_size)) * static_cast(snap_grid_size); + new_pos = glm::round(new_pos / static_cast(snap_grid_size)) * static_cast(snap_grid_size); auto pm = dynamic_cast(m_dragged_object.get()); if (pm) From 46bc330ede18a275f0e4778ced6fd280f0c5883e Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 25 Aug 2025 11:30:05 +0200 Subject: [PATCH 110/141] Prevent buttons from having mouse focus after being clicked This prevents an endless "testing loop" when clicking the "Test from here" button in the editor --- src/interface/control_button.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/control_button.cpp b/src/interface/control_button.cpp index 88a5ee6336..52c06ef1b8 100644 --- a/src/interface/control_button.cpp +++ b/src/interface/control_button.cpp @@ -62,7 +62,7 @@ ControlButton::on_mouse_button_up(const SDL_MouseButtonEvent& button) call_on_activate_callbacks(); - m_has_focus = true; + m_has_focus = false; return true; } From 93534af9f6541b11a03c63ebf487e4ad34e9bacb Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 25 Aug 2025 11:46:35 +0200 Subject: [PATCH 111/141] Add delete tool to top editor toolbar --- src/editor/editor.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 86f39f7769..3b4782d325 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -177,7 +177,7 @@ Editor::Editor() : m_widgets.push_back(std::move(layers_widget)); m_widgets.push_back(std::move(overlay_widget)); - std::array, 7> general_widgets = { + std::array, 8> general_widgets = { // Undo button std::make_unique("images/engine/editor/undo.png", std::bind(&Editor::undo, this), @@ -233,7 +233,14 @@ Editor::Editor() : [this]() { m_toolbox_widget->set_mouse_tool(); }, - _("Toggle between add and remove mode")) + _("Toggle between add and remove mode")), + + // Rubber button + std::make_unique("images/engine/editor/rubber.png", + [this]() { + m_toolbox_widget->set_rubber_tool(); + }, + _("Delete the tile or object under the mouse")) }; std::array, 4> tile_mode_widgets = { From 5d60e78cbc88a158cab623a3ac3aec20aa38938a Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:09:13 +0300 Subject: [PATCH 112/141] MenuManager: Only reactivate Editor when menu stack is empty; Editor re-activation safeguard [ci skip] --- src/editor/editor.cpp | 29 ++++++++++++++++------------- src/gui/menu_manager.cpp | 3 ++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 3b4782d325..5d12500748 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -520,18 +520,27 @@ Editor::update(float dt_sec, const Controller& controller) // Create new level. } + if (m_deactivate_request) { + m_enabled = false; + m_deactivate_request = false; + return; + } + if (m_reactivate_request) { - m_enabled = true; m_reactivate_request = false; - // It's possible that the editor is being re-activated due to exiting a menu, - // possibly one related to an object option. - GameObject* selected_object = m_selected_object.get(); - if (selected_object) + if (!m_enabled) { - selected_object->after_editor_set(); - selected_object->check_state(); + // It's possible that the editor is being re-activated due to exiting a menu, + // possibly one related to an object option. + GameObject* selected_object = m_selected_object.get(); + if (selected_object) + { + selected_object->after_editor_set(); + selected_object->check_state(); + } } + m_enabled = true; } if (m_save_request) { @@ -559,12 +568,6 @@ Editor::update(float dt_sec, const Controller& controller) return; } - if (m_deactivate_request) { - m_enabled = false; - m_deactivate_request = false; - return; - } - // Update other components. if (m_levelloaded && !m_leveltested) { BIND_SECTOR(*m_sector); diff --git a/src/gui/menu_manager.cpp b/src/gui/menu_manager.cpp index e86f5ed4c9..22a26ed096 100644 --- a/src/gui/menu_manager.cpp +++ b/src/gui/menu_manager.cpp @@ -277,7 +277,8 @@ MenuManager::pop_menu(bool skip_transition) m_menu_stack.size() >= 2 ? m_menu_stack[m_menu_stack.size() - 2].get() : nullptr); m_menu_stack.pop_back(); - Editor::may_reactivate(); + if (m_menu_stack.empty()) + Editor::may_reactivate(); } void From fd1c6a3d3e7bdffc705c3c55202a1dc7f8f0c3fb Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 25 Aug 2025 12:26:23 +0200 Subject: [PATCH 113/141] Set mouse tool when opening editor --- src/editor/toolbox_widget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index 876976395e..fd4e666f6c 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -55,6 +55,8 @@ EditorToolboxWidget::EditorToolboxWidget(Editor& editor) : m_move_mode->push_mode("images/engine/editor/move-mode1.png"); m_undo_mode->push_mode("images/engine/editor/redo.png"); //settings_mode->push_mode("images/engine/editor/settings-mode1.png"); + + set_mouse_tool(); } void From e4add8f3615bc332dbe0667fcd53814c9fb88f59 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 26 Aug 2025 14:33:59 -0400 Subject: [PATCH 114/141] Blur effect --- data/shader/shader330.frag | 27 +++++++++++++++++++++++++-- src/editor/editor.cpp | 2 ++ src/editor/layers_widget.cpp | 2 ++ src/editor/tilebox.cpp | 5 ++++- src/editor/toolbox_widget.cpp | 3 +++ src/supertux/gameconfig.cpp | 3 +++ src/supertux/gameconfig.hpp | 1 + src/supertux/menu/editor_settings.cpp | 1 + src/video/canvas.cpp | 4 +++- src/video/canvas.hpp | 5 ++++- src/video/drawing_request.hpp | 4 +++- src/video/gl/gl20_context.cpp | 3 +++ src/video/gl/gl20_context.hpp | 2 ++ src/video/gl/gl33core_context.cpp | 15 ++++++++++++--- src/video/gl/gl33core_context.hpp | 3 +++ src/video/gl/gl_context.hpp | 2 ++ src/video/gl/gl_painter.cpp | 2 ++ src/video/gl/gl_program.cpp | 1 + src/video/gl/gl_program.hpp | 2 ++ 19 files changed, 78 insertions(+), 9 deletions(-) diff --git a/data/shader/shader330.frag b/data/shader/shader330.frag index 291be7547c..a92eecb075 100644 --- a/data/shader/shader330.frag +++ b/data/shader/shader330.frag @@ -9,6 +9,7 @@ uniform float game_time; uniform vec2 animate; uniform vec2 displacement_animate; uniform bool is_displacement; +uniform int blur; in vec4 diffuse_var; in vec2 texcoord_var; @@ -19,8 +20,30 @@ void main(void) { if (backbuffer == 0.0 || !is_displacement) { - vec4 color = diffuse_var * texture(diffuse_texture, texcoord_var.st + (animate * game_time)); - fragColor = color; + vec4 color = diffuse_var * texture(diffuse_texture, texcoord_var.st + (animate * game_time)); + if (blur != 0.0) + { + vec2 uv = (fragcoord2uv * gl_FragCoord.xyw).xy; + vec2 texel = vec2(fragcoord2uv[0].x, fragcoord2uv[1].y); + float num = 0.0; + vec4 sum = vec4(0.0); + + for (float y = -blur; y <= blur; ++y) + { + for (float x = -blur; x <= blur; ++x) + { + vec2 offset = vec2(x, y) * texel; + sum += texture(framebuffer_texture, uv + offset); + num++; + } + } + + fragColor = vec4(mix(color.rgb, (sum/num).rgb * 1.05, color.a), 1.0); + } + else + { + fragColor = color; + } } else if (is_displacement) { diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 5d12500748..bae3e85e3e 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -383,11 +383,13 @@ Editor::draw(Compositor& compositor) if (get_properties_panel_visible()) { + context.color().set_blur(g_config->editor_blur); context.color().draw_filled_rect(Rectf(0.0f, 0.0f, SCREEN_WIDTH, 32.0f), Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); context.color().draw_filled_rect(Rectf(0, 32.0f, 200.0f, SCREEN_HEIGHT - 32.0f), Color(0.2f, 0.2f, 0.2f, 0.5f), LAYER_GUI - 6); + context.color().set_blur(0); for(const auto& control : m_controls) { diff --git a/src/editor/layers_widget.cpp b/src/editor/layers_widget.cpp index 344fa18a7c..0ae5781d77 100644 --- a/src/editor/layers_widget.cpp +++ b/src/editor/layers_widget.cpp @@ -81,11 +81,13 @@ EditorLayersWidget::draw(DrawingContext& context) m_object_tip->draw_up(context, position); } + context.color().set_blur(g_config->editor_blur); context.color().draw_filled_rect(Rectf(Vector(0, static_cast(m_Ypos)), Vector(static_cast(m_Width), static_cast(SCREEN_HEIGHT))), g_config->editorcolor, 0.0f, LAYER_GUI-10); + context.color().set_blur(0); Rectf target_rect = Rectf(0, 0, 0, 0); bool draw_rect = true; diff --git a/src/editor/tilebox.cpp b/src/editor/tilebox.cpp index 6f1faeeb47..209d282135 100644 --- a/src/editor/tilebox.cpp +++ b/src/editor/tilebox.cpp @@ -30,6 +30,7 @@ #include "supertux/resources.hpp" #include "util/log.hpp" #include "video/drawing_context.hpp" +#include "video/video_system.hpp" EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : m_editor(editor), @@ -58,10 +59,12 @@ EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : void EditorTilebox::draw(DrawingContext& context) { + context.color().set_blur(g_config->editor_blur); context.color().draw_filled_rect(m_rect, g_config->editorcolor, 0.0f, LAYER_GUI-10); - + context.color().set_blur(0); + if (m_dragging) { context.color().draw_filled_rect(selection_draw_rect(), Color(0.2f, 0.4f, 1.0f, 0.6f), diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index fd4e666f6c..d1ae05593e 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -64,10 +64,13 @@ EditorToolboxWidget::draw(DrawingContext& context) { m_tilebox->draw(context); + + context.color().set_blur(g_config->editor_blur); context.color().draw_filled_rect(Rectf(Vector(m_pos_x, 0.f), Vector(context.get_width(), 96.f)), g_config->editorcolor, 0.0f, LAYER_GUI-10); + context.color().set_blur(0); if (m_hovered_item != HoveredItem::NONE && m_hovered_item != HoveredItem::TILEBOX) { diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index 2110383ebf..313eed528f 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -112,6 +112,7 @@ Config::Config() : editor_show_deprecated_tiles(false), editor_show_properties_sidebar(true), editor_show_toolbar_widgets(true), + editor_blur(15), multiplayer_auto_manage_players(true), multiplayer_multibind(false), #if SDL_VERSION_ATLEAST(2, 0, 9) @@ -252,6 +253,7 @@ Config::load() editor_mapping->get("show_deprecated_tiles", editor_show_deprecated_tiles); editor_mapping->get("show_properties_sidebar", editor_show_properties_sidebar); editor_mapping->get("show_toolbar_widgets", editor_show_toolbar_widgets); + editor_mapping->get("blur", editor_blur); } if (is_christmas()) { @@ -513,6 +515,7 @@ Config::save() writer.write("show_deprecated_tiles", editor_show_deprecated_tiles); writer.write("show_properties_sidebar", editor_show_properties_sidebar); writer.write("show_toolbar_widgets", editor_show_toolbar_widgets); + writer.write("blur", editor_blur); } writer.end_list("editor"); diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index 22fa7c08e5..93c86901a4 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -153,6 +153,7 @@ class Config final bool editor_show_deprecated_tiles; bool editor_show_properties_sidebar; bool editor_show_toolbar_widgets; + int editor_blur; std::string preferred_text_editor; bool multiplayer_auto_manage_players; diff --git a/src/supertux/menu/editor_settings.cpp b/src/supertux/menu/editor_settings.cpp index f991035ca3..9c7d7ce625 100644 --- a/src/supertux/menu/editor_settings.cpp +++ b/src/supertux/menu/editor_settings.cpp @@ -42,6 +42,7 @@ EditorSettings::EditorSettings() add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); add_toggle(-1, _("Show Properties Sidebar"), &(g_config->editor_show_properties_sidebar)); add_toggle(-1, _("Show Toolbar"), &(g_config->editor_show_toolbar_widgets)); + add_intfield(_("Blur Amount"), &(g_config->editor_blur), -1, true); add_textfield(_("Preferred Text Editor"), &(g_config->preferred_text_editor)); if (g_config->editor_undo_tracking) { diff --git a/src/video/canvas.cpp b/src/video/canvas.cpp index 103bdbfb5c..b2870fcb0a 100644 --- a/src/video/canvas.cpp +++ b/src/video/canvas.cpp @@ -32,7 +32,8 @@ Canvas::Canvas(DrawingContext& context, obstack& obst) : m_context(context), m_obst(obst), - m_requests() + m_requests(), + m_blur(0) { m_requests.reserve(500); } @@ -282,6 +283,7 @@ Canvas::draw_filled_rect(const Rectf& rect, const Color& color, float radius, in request->color = color; request->color.alpha = color.alpha * m_context.transform().alpha; request->radius = radius; + request->blur = m_blur; m_requests.push_back(request); } diff --git a/src/video/canvas.hpp b/src/video/canvas.hpp index 0e33019d57..948d8d0a80 100644 --- a/src/video/canvas.hpp +++ b/src/video/canvas.hpp @@ -47,7 +47,7 @@ class Canvas final public: Canvas(DrawingContext& context, obstack& obst); ~Canvas(); - + void draw_surface(const SurfacePtr& surface, const Vector& position, int layer); void draw_surface(const SurfacePtr& surface, const Vector& position, float angle, const Color& color, const Blend& blend, int layer); @@ -96,6 +96,8 @@ class Canvas final void clear(); void render(Renderer& renderer, Filter filter); + + void set_blur(int blur) { m_blur = blur; } inline DrawingContext& get_context() { return m_context; } @@ -106,6 +108,7 @@ class Canvas final private: DrawingContext& m_context; obstack& m_obst; + int m_blur; std::vector m_requests; private: diff --git a/src/video/drawing_request.hpp b/src/video/drawing_request.hpp index a34aaafa26..0f3b916665 100644 --- a/src/video/drawing_request.hpp +++ b/src/video/drawing_request.hpp @@ -110,7 +110,8 @@ struct FillRectRequest : public DrawingRequest DrawingRequest(transform), rect(), color(), - radius() + radius(), + blur() {} RequestType get_type() const override { return RequestType::FILLRECT; } @@ -118,6 +119,7 @@ struct FillRectRequest : public DrawingRequest Rectf rect; Color color; float radius; + int blur; }; struct InverseEllipseRequest : public DrawingRequest diff --git a/src/video/gl/gl20_context.cpp b/src/video/gl/gl20_context.cpp index 11e24c795f..d8020fcd3a 100644 --- a/src/video/gl/gl20_context.cpp +++ b/src/video/gl/gl20_context.cpp @@ -135,6 +135,9 @@ GL20Context::set_color(const Color& color) assert_gl(); } +void +GL20Context::set_blur(int amount) {} + void GL20Context::bind_texture(const Texture& texture, const Texture* displacement_texture) { diff --git a/src/video/gl/gl20_context.hpp b/src/video/gl/gl20_context.hpp index d1d7b0909a..745abce718 100644 --- a/src/video/gl/gl20_context.hpp +++ b/src/video/gl/gl20_context.hpp @@ -41,6 +41,8 @@ class GL20Context final : public GLContext virtual void set_colors(const float* data, size_t size) override; virtual void set_color(const Color& color) override; + + virtual void set_blur(int amount) override; virtual void bind_texture(const Texture& texture, const Texture* displacement_texture) override; virtual void bind_no_texture() override; diff --git a/src/video/gl/gl33core_context.cpp b/src/video/gl/gl33core_context.cpp index 928c917f00..cfd63612b4 100644 --- a/src/video/gl/gl33core_context.cpp +++ b/src/video/gl/gl33core_context.cpp @@ -34,7 +34,8 @@ GL33CoreContext::GL33CoreContext(GLVideoSystem& video_system) : m_white_texture(), m_black_texture(), m_grey_texture(), - m_transparent_texture() + m_transparent_texture(), + m_blur() { assert_gl(); @@ -98,8 +99,8 @@ GL33CoreContext::bind() const float matrix[3*3] = { sx, 0.0, 0, - 0.0, sy, 0, - tx, ty, 1.0, + 0.0, -sy, 0, + tx, 1.0f - ty, 1.0, }; glUniformMatrix3fv(m_program->get_fragcoord2uv_location(), 1, false, matrix); @@ -176,6 +177,12 @@ GL33CoreContext::set_color(const Color& color) m_vertex_arrays->set_color(color); } +void +GL33CoreContext::set_blur(int amount) +{ + m_blur = amount; +} + void GL33CoreContext::bind_texture(const Texture& texture, const Texture* displacement_texture) { @@ -183,6 +190,7 @@ GL33CoreContext::bind_texture(const Texture& texture, const Texture* displacemen GLTextureRenderer* back_renderer = static_cast(m_video_system.get_back_renderer()); + glUniform1i(m_program->get_blur_location(), m_blur); if (displacement_texture && back_renderer->is_rendering()) { glActiveTexture(GL_TEXTURE0); @@ -231,6 +239,7 @@ GL33CoreContext::bind_no_texture() { assert_gl(); + glUniform1i(m_program->get_blur_location(), m_blur); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_white_texture->get_handle()); diff --git a/src/video/gl/gl33core_context.hpp b/src/video/gl/gl33core_context.hpp index 529cca8cf1..808854c34e 100644 --- a/src/video/gl/gl33core_context.hpp +++ b/src/video/gl/gl33core_context.hpp @@ -46,6 +46,8 @@ class GL33CoreContext final : public GLContext virtual void set_colors(const float* data, size_t size) override; virtual void set_color(const Color& color) override; + + virtual void set_blur(int amount) override; virtual void bind_texture(const Texture& texture, const Texture* displacement_texture) override; virtual void bind_no_texture() override; @@ -65,6 +67,7 @@ class GL33CoreContext final : public GLContext std::unique_ptr m_black_texture; std::unique_ptr m_grey_texture; std::unique_ptr m_transparent_texture; + int m_blur; private: GL33CoreContext(const GL33CoreContext&) = delete; diff --git a/src/video/gl/gl_context.hpp b/src/video/gl/gl_context.hpp index c55bf56ba3..ab95bd89ae 100644 --- a/src/video/gl/gl_context.hpp +++ b/src/video/gl/gl_context.hpp @@ -38,6 +38,8 @@ class GLContext virtual void ortho(float width, float height, bool vflip) = 0; virtual void blend_func(GLenum src, GLenum dst) = 0; + + virtual void set_blur(int blur) = 0; virtual void set_positions(const float* data, size_t size) = 0; diff --git a/src/video/gl/gl_painter.cpp b/src/video/gl/gl_painter.cpp index 382606c3cd..a43e56d945 100644 --- a/src/video/gl/gl_painter.cpp +++ b/src/video/gl/gl_painter.cpp @@ -237,6 +237,7 @@ GLPainter::draw_filled_rect(const FillRectRequest& request) GLContext& context = m_video_system.get_context(); + context.set_blur(request.blur); context.blend_func(sfactor(request.blend), dfactor(request.blend)); context.bind_no_texture(); context.set_texcoord(0.0f, 0.0f); @@ -307,6 +308,7 @@ GLPainter::draw_filled_rect(const FillRectRequest& request) context.draw_arrays(GL_TRIANGLE_FAN, 0, 4); } + context.set_blur(0); assert_gl(); } diff --git a/src/video/gl/gl_program.cpp b/src/video/gl/gl_program.cpp index 39ab329b25..4a7a7b79e5 100644 --- a/src/video/gl/gl_program.cpp +++ b/src/video/gl/gl_program.cpp @@ -74,6 +74,7 @@ GLProgram::GLProgram() : m_animate_location = glGetUniformLocation(m_program, "animate"); m_displacement_animate_location = glGetUniformLocation(m_program, "displacement_animate"); m_is_displacement_location = glGetUniformLocation(m_program, "is_displacement"); + m_blur_location = glGetUniformLocation(m_program, "blur"); m_position_location = glGetAttribLocation(m_program, "position"); m_texcoord_location = glGetAttribLocation(m_program, "texcoord"); m_diffuse_location = glGetAttribLocation(m_program, "diffuse"); diff --git a/src/video/gl/gl_program.hpp b/src/video/gl/gl_program.hpp index 7fb16163cd..f29bd06c0d 100644 --- a/src/video/gl/gl_program.hpp +++ b/src/video/gl/gl_program.hpp @@ -46,6 +46,7 @@ class GLProgram final inline GLint get_texcoord_location() const { return check_valid(m_texcoord_location, "texcoord"); } inline GLint get_diffuse_location() const { return check_valid(m_diffuse_location, "diffuse"); } inline GLint get_is_displacement_location() const { return check_valid(m_is_displacement_location, "is_displacement"); } + inline GLint get_blur_location() const { return check_valid(m_blur_location, "blur"); } private: bool get_link_status() const; @@ -72,6 +73,7 @@ class GLProgram final GLint m_texcoord_location; GLint m_diffuse_location; GLint m_is_displacement_location; + GLint m_blur_location; private: GLProgram(const GLProgram&) = delete; From bce44bf9f04758c2cac1beb4d10ec652d15cd7d1 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 26 Aug 2025 15:58:10 -0400 Subject: [PATCH 115/141] Shadow and lines on sidebar/layers widget --- data/images/engine/editor/shadow.png | Bin 0 -> 219 bytes src/editor/editor.cpp | 30 ++++++++++++++++++++++++++- src/editor/editor.hpp | 2 ++ src/editor/toolbox_widget.cpp | 1 - 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 data/images/engine/editor/shadow.png diff --git a/data/images/engine/editor/shadow.png b/data/images/engine/editor/shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..05e86778ad7e6e6711ddb13780f842ff99a3f0a2 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-r!3HGvcdbbWaf*Z7ofvPP)Tsw@SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YPV z+ueoXKL{?^yL>WGgtNdSvY3HEPZ@+6E0)@q0R`DhJbhi+UvfwZ>!`7dzh4CulJ|6R z4B@z*oRW}`kOsu*2?-Ar8Y~x@SWC?pI>X0co5Ju%TrBYdP$`3_tDnm{r-UW|s;4*a literal 0 HcmV?d00001 diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index bae3e85e3e..b418fd2924 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -163,7 +163,8 @@ Editor::Editor() : m_script_manager(), m_on_exit_cb(nullptr), m_save_temp_level(false), - m_last_test_pos(std::nullopt) + m_last_test_pos(std::nullopt), + m_shadow(SpriteManager::current()->create("images/engine/editor/shadow.png")) { auto toolbox_widget = std::make_unique(*this); auto layers_widget = std::make_unique(*this); @@ -465,6 +466,33 @@ Editor::draw(Compositor& compositor) m_controls.clear(); } } + + // BEGIN Draw shadows and line + constexpr float LINE_THICKNESS = 1.f; + Rectf border_rect = Rectf{SCREEN_WIDTH - 128.f - LINE_THICKNESS, 0, + SCREEN_WIDTH - 128.f, static_cast(SCREEN_HEIGHT - 32.f)}; + Color line_color = g_config->editorcolor; + line_color.red -= 0.2; + line_color.green -= 0.2; + line_color.blue -= 0.2; + line_color.alpha -= 0.2; + context.color().draw_filled_rect(border_rect, line_color, LAYER_GUI + 1); + + if (m_shadow) + { + Rectf shadow_rect = border_rect; + shadow_rect.set_left(border_rect.get_left() - 16 + LINE_THICKNESS); + shadow_rect.set_right(border_rect.get_right() - LINE_THICKNESS); + context.set_alpha(0.2); + m_shadow->draw_scaled(context.color(), shadow_rect, LAYER_GUI + 1); + context.set_alpha(1.0); + } + + + Rectf layers_rect = Rectf{0, SCREEN_HEIGHT - 32.f - LINE_THICKNESS, + SCREEN_WIDTH - 128.f, SCREEN_HEIGHT - 32.f}; + context.color().draw_filled_rect(layers_rect, line_color, LAYER_GUI + 1); + // END Draw shadows and line context.color().draw_filled_rect(context.get_rect(), Color(0.0f, 0.0f, 0.0f), diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 1e981c0d3d..1fa9e92d6d 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -281,6 +281,8 @@ class Editor final : public Screen, Vector m_mouse_pos; bool m_layers_widget_needs_refresh; + + SpritePtr m_shadow; private: Editor(const Editor&) = delete; diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index d1ae05593e..0e4315cdef 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -63,7 +63,6 @@ void EditorToolboxWidget::draw(DrawingContext& context) { m_tilebox->draw(context); - context.color().set_blur(g_config->editor_blur); context.color().draw_filled_rect(Rectf(Vector(m_pos_x, 0.f), From 38015d0e11f022de5e7e662af8f2221bf979c044 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Tue, 26 Aug 2025 19:53:52 -0400 Subject: [PATCH 116/141] Show scroll shadow in tilebox --- data/images/engine/editor/shadow2.png | Bin 0 -> 240 bytes src/editor/tilebox.cpp | 18 +++++++++++++++++- src/editor/tilebox.hpp | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 data/images/engine/editor/shadow2.png diff --git a/data/images/engine/editor/shadow2.png b/data/images/engine/editor/shadow2.png new file mode 100644 index 0000000000000000000000000000000000000000..0d1b93cffa894e9d2b7aaa1f7b2bc5e9dd0135d9 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfw!3HERKNXS!2^0spJ2BoosZ$T+u%tWsIx;Y9 z?C1WI$O=-K>=ES4z)+>ez|hdb!0-zw)bN6Vq11qZ;Z*_ygVhWM2JwP9y8>;15^PD{ z?k)`fL2$v|<&%LToCO|{#S9F3${@^GvDCf{D9B#o>Fdh=l0!;dRl#C^B*<+ho-U3d z9M|V|?c`-p;5jm<$PyF+GaXXKZB>MpUXO@geCy>1w*R< literal 0 HcmV?d00001 diff --git a/src/editor/tilebox.cpp b/src/editor/tilebox.cpp index 209d282135..f09a6008aa 100644 --- a/src/editor/tilebox.cpp +++ b/src/editor/tilebox.cpp @@ -21,6 +21,8 @@ #include "editor/object_info.hpp" #include "editor/tile_selection.hpp" #include "editor/tip.hpp" +#include "sprite/sprite_ptr.hpp" +#include "sprite/sprite_manager.hpp" #include "supertux/colorscheme.hpp" #include "supertux/debug.hpp" #include "supertux/gameconfig.hpp" @@ -31,6 +33,7 @@ #include "util/log.hpp" #include "video/drawing_context.hpp" #include "video/video_system.hpp" +#include EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : m_editor(editor), @@ -51,7 +54,8 @@ EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : m_hovered_tile(-1), m_dragging(false), m_drag_start(0, 0), - m_mouse_pos(0, 0) + m_mouse_pos(0, 0), + m_shadow(SpriteManager::current()->create("images/engine/editor/shadow2.png")) { m_scrollbar.reset(new ControlScrollbar(1.f, 1.f, m_scroll_progress, 35.f)); } @@ -79,6 +83,18 @@ EditorTilebox::draw(DrawingContext& context) g_config->editorhovercolor, 0.0f, LAYER_GUI - 5); } + + // Shadow + constexpr float SCROLL_SHADOW_MAX = 10.f; + float scroll_shadow_size = std::clamp(m_scroll_progress * 0.1, 0.f, SCROLL_SHADOW_MAX); + float scroll_shadow_normal = scroll_shadow_size / SCROLL_SHADOW_MAX; + + context.set_alpha(scroll_shadow_normal * 0.3); + m_shadow->draw_scaled( + context.color(), + Rectf{m_rect.get_left(), m_rect.get_top(), m_rect.get_right(), m_rect.get_top() + scroll_shadow_size}, + LAYER_GUI+1); + context.set_alpha(1.0); context.push_transform(); context.set_viewport(Rect(m_rect)); diff --git a/src/editor/tilebox.hpp b/src/editor/tilebox.hpp index e52c1b4dc7..02c0310764 100644 --- a/src/editor/tilebox.hpp +++ b/src/editor/tilebox.hpp @@ -25,6 +25,8 @@ #include "interface/control_scrollbar.hpp" #include "math/rectf.hpp" #include "math/vector.hpp" +#include "sprite/sprite.hpp" +#include "sprite/sprite_ptr.hpp" #include "supertux/tile_set.hpp" class Editor; @@ -141,6 +143,8 @@ class EditorTilebox final : public Widget Vector m_drag_start; Vector m_mouse_pos; + + SpritePtr m_shadow; private: EditorTilebox(const EditorTilebox&) = delete; From 0191b3e0afbeb258f62d14a08d0b75f26e3304c8 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Wed, 27 Aug 2025 09:46:00 +0200 Subject: [PATCH 117/141] ControlTextbox: Pressing return reliinquishes focus --- src/interface/control_textbox.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interface/control_textbox.cpp b/src/interface/control_textbox.cpp index 3b4d377c2c..c7d1d15468 100644 --- a/src/interface/control_textbox.cpp +++ b/src/interface/control_textbox.cpp @@ -295,6 +295,7 @@ ControlTextbox::on_key_down(const SDL_KeyboardEvent& key) } else if (key.keysym.sym == SDLK_RETURN) { + m_has_focus = false; parse_value(); return true; } From e7d649aab31a666a2ed815bdb47b7a5696c2e6a8 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Thu, 28 Aug 2025 23:34:49 +0300 Subject: [PATCH 118/141] Editor: Never accept events if MenuManager has active menu or dialog --- src/editor/editor.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index b418fd2924..d01c3a72d6 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -796,9 +796,8 @@ Editor::esc_press() void Editor::update_keyboard(const Controller& controller) { - if (!m_enabled) { + if (!m_enabled) return; - } if (MenuManager::instance().is_active() || MenuManager::instance().has_dialog()) return; @@ -904,9 +903,10 @@ Editor::set_level(std::unique_ptr level, bool reset) m_levelloaded = true; - if (level != nullptr) { + if (level != nullptr) + { // Reload level. - m_level = std::move(level); + m_level = std::move(level); } else { @@ -1174,10 +1174,11 @@ Editor::setup() Tile::draw_editor_images = true; Sector::s_draw_solids_only = false; m_after_setup = true; - if (!m_levelloaded) { - + if (!m_levelloaded) + { #if 0 - if (AddonManager::current()->is_old_addon_enabled()) { + if (AddonManager::current()->is_old_addon_enabled()) + { auto dialog = std::make_unique(); dialog->set_text(_("Some obsolete add-ons are still active\nand might cause collisions with the default SuperTux structure.\nYou can still enable these add-ons in the menu.\nDisabling these add-ons will not delete your game progress.")); dialog->clear_buttons(); @@ -1196,7 +1197,8 @@ Editor::setup() }); MenuManager::instance().set_dialog(std::move(dialog)); - } else + } + else #endif { MenuManager::instance().push_menu(MenuStorage::EDITOR_LEVELSET_SELECT_MENU); @@ -1243,7 +1245,8 @@ Editor::on_window_resize() void Editor::event(const SDL_Event& ev) { - if (!m_enabled || !m_levelloaded) return; + if (!m_enabled || !m_levelloaded || + MenuManager::current()->is_active() || MenuManager::current()->has_dialog()) return; for(const auto& control : m_controls) if (control->event(ev)) From 687cffc059d02f9bcceb6eb750405e9c1bd493cf Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Thu, 28 Aug 2025 23:39:58 +0300 Subject: [PATCH 119/141] Translation fix with "Save Level as" menu label [ci skip] --- src/supertux/menu/editor_levelset_select_menu.cpp | 2 +- src/supertux/menu/editor_levelset_select_menu.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/supertux/menu/editor_levelset_select_menu.cpp b/src/supertux/menu/editor_levelset_select_menu.cpp index f1b04776b2..9463232fe4 100644 --- a/src/supertux/menu/editor_levelset_select_menu.cpp +++ b/src/supertux/menu/editor_levelset_select_menu.cpp @@ -72,7 +72,7 @@ EditorLevelsetSelectMenu::initialize() return false; }); - add_label(_(m_save_as ? "Save level as..." : "Choose World")); + add_label(m_save_as ? _("Save Level as") : _("Choose World")); add_hl(); int i = 0; diff --git a/src/supertux/menu/editor_levelset_select_menu.hpp b/src/supertux/menu/editor_levelset_select_menu.hpp index 5f892a97ff..b3f4447ef4 100644 --- a/src/supertux/menu/editor_levelset_select_menu.hpp +++ b/src/supertux/menu/editor_levelset_select_menu.hpp @@ -22,7 +22,7 @@ class EditorLevelsetSelectMenu : public Menu { private: std::vector m_contrib_worlds; - bool m_save_as; + const bool m_save_as; public: EditorLevelsetSelectMenu(bool save_as = false); From 5886b6e556cf5560b9b2cf45730c23ac6397b192 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 29 Aug 2025 03:11:20 +0300 Subject: [PATCH 120/141] `Notification`: Support automatically fading out and closing after a set time, no default mini text --- src/editor/editor.cpp | 2 +- src/gui/notification.cpp | 54 ++++++++++++++++++----- src/gui/notification.hpp | 14 +++--- src/supertux/main.cpp | 3 +- src/supertux/menu/editor_temp_save_as.cpp | 2 +- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index d01c3a72d6..46b75b5d0b 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -217,7 +217,7 @@ Editor::Editor() : [this] { if (save_level()) { - auto notif = std::make_unique("save_level_notif", false, true); + auto notif = std::make_unique("save_level_notif", 5.f); notif->set_text(_("Level saved!")); MenuManager::instance().set_notification(std::move(notif)); } diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index c2ee25fea7..68a750358a 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -31,10 +31,14 @@ #include "util/gettext.hpp" #include "util/log.hpp" -Notification::Notification(std::string id, bool no_auto_hide, bool no_auto_disable) : +Notification::Notification(const std::string& id, float idle_close_time, + bool no_auto_close, bool auto_disable) : m_id(id), - m_auto_hide(!no_auto_hide), - m_auto_disable(!no_auto_disable), + m_idle_close_time(idle_close_time), + m_auto_close(!no_auto_close), + m_auto_disable(auto_disable), + m_idle_close_timer(), + m_alpha(1.f), m_text(), m_mini_text(), m_text_size(), @@ -55,7 +59,7 @@ Notification::Notification(std::string id, bool no_auto_hide, bool no_auto_disab return; } - set_mini_text(_("Click for more details.")); // Set default mini text. + m_idle_close_timer.start(m_idle_close_time); } Notification::~Notification() @@ -92,13 +96,26 @@ Notification::calculate_size() void Notification::draw(DrawingContext& context) { - // Close notification, if a quit has been requested, or the MenuManager isn't active. + // Close notification, if a quit has been requested, or neither the MenuManager or Editor aren't active. if (m_quit || !(MenuManager::instance().is_active() || Editor::is_active())) { close(); return; } + if (m_alpha < 1.f || m_idle_close_timer.check()) + { + m_alpha -= 0.01f; + if (m_alpha <= 0.f) + { + close(); + return; + } + } + + context.push_transform(); + context.set_alpha(m_alpha); + m_pos = Vector(context.get_width() - std::max(m_text_size.width, m_mini_text_size.width) - 90.0f, static_cast(context.get_height() / 12) - m_text_size.height - m_mini_text_size.height + 10.0f); Rectf bg_rect(m_pos, m_size); @@ -129,8 +146,8 @@ Notification::draw(DrawingContext& context) // Draw "Do not show again" and "Close" symbols, if the mouse is hovering over the notification. if (!m_mouse_over) return; - const std::string sym1 = "-"; - const std::string sym2 = "X"; + static const std::string sym1 = "-"; + static const std::string sym2 = "X"; Vector sym1_pos = Vector(bg_rect.get_left() + 5.0f, bg_rect.get_top()); Vector sym2_pos = Vector(bg_rect.get_right() - 15.0f, bg_rect.get_top()); @@ -161,6 +178,8 @@ Notification::draw(DrawingContext& context) m_mouse_pos.y + 20.0f), ALIGN_RIGHT, LAYER_GUI + 1, Color::CYAN); } + + context.pop_transform(); } void @@ -189,7 +208,7 @@ Notification::event(const SDL_Event& ev) { m_callback(); if (m_auto_disable) disable(); - if (m_auto_hide) close(); + if (m_auto_close) close(); } } } @@ -199,7 +218,19 @@ Notification::event(const SDL_Event& ev) { m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y); m_mouse_over = bg_rect.contains(m_mouse_pos); - if (MouseCursor::current() && m_mouse_over) MouseCursor::current()->set_state(MouseCursorState::LINK); + + if (m_mouse_over) + { + m_alpha = 1.f; + m_idle_close_timer.stop(); + } + else if (!m_idle_close_timer.started()) + { + m_idle_close_timer.start(m_idle_close_time); + } + + if (MouseCursor::current() && m_mouse_over) + MouseCursor::current()->set_state(MouseCursorState::LINK); } break; @@ -240,14 +271,15 @@ Notification::disable() void Notification::close() { - if (MouseCursor::current() && m_mouse_over) MouseCursor::current()->set_state(MouseCursorState::NORMAL); + if (MouseCursor::current() && m_mouse_over) + MouseCursor::current()->set_state(MouseCursorState::NORMAL); MenuManager::instance().set_notification({}); } // Static functions, serving as utilities bool -Notification::is_disabled(std::string id) // Check if a notification is disabled by its ID. +Notification::is_disabled(const std::string& id) // Check if a notification is disabled by its ID. { return std::any_of(g_config->notifications.begin(), g_config->notifications.end(), [id](const auto& notif) diff --git a/src/gui/notification.hpp b/src/gui/notification.hpp index d40fe42356..5e20997e66 100644 --- a/src/gui/notification.hpp +++ b/src/gui/notification.hpp @@ -22,15 +22,20 @@ #include "control/controller.hpp" #include "math/sizef.hpp" +#include "supertux/timer.hpp" #include "video/drawing_context.hpp" class Notification { private: const std::string m_id; - const bool m_auto_hide; + const float m_idle_close_time; + const bool m_auto_close; const bool m_auto_disable; + Timer m_idle_close_timer; + float m_alpha; + std::string m_text; std::string m_mini_text; Sizef m_text_size; @@ -49,7 +54,8 @@ class Notification std::function m_callback; public: - Notification(std::string id, bool no_auto_hide = false, bool no_auto_disable = false); + Notification(const std::string& id, float idle_close_time = 0.f, + bool no_auto_close = false, bool auto_disable = false); ~Notification(); void set_text(const std::string& text); @@ -61,13 +67,11 @@ class Notification void draw(DrawingContext& context); // Notification actions - void disable(); void close(); // Static functions, serving as utilities - - static bool is_disabled(std::string id); + static bool is_disabled(const std::string& id); private: void calculate_size(); diff --git a/src/supertux/main.cpp b/src/supertux/main.cpp index 1ac3c34220..4ca182515d 100644 --- a/src/supertux/main.cpp +++ b/src/supertux/main.cpp @@ -799,8 +799,9 @@ Main::release_check() const std::string version = version_full.substr(version_full.find("v") + 1, version_full.find("-") - 1); if (version != latest_ver) { - auto notif = std::make_unique("new_release_" + latest_ver); + auto notif = std::make_unique("new_release_" + latest_ver, 20.f, false, true); notif->set_text(fmt::format(fmt::runtime(_("New release: SuperTux v{}!")), latest_ver)); + notif->set_mini_text(_("Click for more details.")); notif->on_press([latest_ver]() { Dialog::show_confirmation(fmt::format(fmt::runtime(_("A new release of SuperTux (v{}) is available!\nFor more information, you can visit the SuperTux website.\n\nDo you want to visit the website now?")), latest_ver), []() diff --git a/src/supertux/menu/editor_temp_save_as.cpp b/src/supertux/menu/editor_temp_save_as.cpp index aad53fb95f..5dc7cc7f4e 100644 --- a/src/supertux/menu/editor_temp_save_as.cpp +++ b/src/supertux/menu/editor_temp_save_as.cpp @@ -86,7 +86,7 @@ EditorTempSaveAs::menu_action(MenuItem& item) editor->set_world(std::move(std::unique_ptr(m_world.release()))); - auto notif = std::make_unique("create_level_notif", false, true); + auto notif = std::make_unique("create_level_notif", 5.f); notif->set_text(_("Level created!")); MenuManager::instance().set_notification(std::move(notif)); From ae505c78ab9e751559b8b11df360171d87a4ec83 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 28 Aug 2025 21:19:51 -0400 Subject: [PATCH 121/141] Show blur even if displacement texture is drawn This shader will need to be changed when the water PR gets merged --- data/shader/shader330.frag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/shader/shader330.frag b/data/shader/shader330.frag index a92eecb075..934036a835 100644 --- a/data/shader/shader330.frag +++ b/data/shader/shader330.frag @@ -18,7 +18,7 @@ out vec4 fragColor; void main(void) { - if (backbuffer == 0.0 || !is_displacement) + if (backbuffer == 0.0 || !is_displacement || blur != 0.0) { vec4 color = diffuse_var * texture(diffuse_texture, texcoord_var.st + (animate * game_time)); if (blur != 0.0) From 26c96b5b7f0f49391a2e35ed2c4f818669cad907 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 28 Aug 2025 21:23:00 -0400 Subject: [PATCH 122/141] Don't flip back renderer --- data/shader/shader330.frag | 1 + src/video/gl/gl33core_context.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/shader/shader330.frag b/data/shader/shader330.frag index 934036a835..2ee8234462 100644 --- a/data/shader/shader330.frag +++ b/data/shader/shader330.frag @@ -24,6 +24,7 @@ void main(void) if (blur != 0.0) { vec2 uv = (fragcoord2uv * gl_FragCoord.xyw).xy; + uv.y = 1.0 - uv.y; vec2 texel = vec2(fragcoord2uv[0].x, fragcoord2uv[1].y); float num = 0.0; vec4 sum = vec4(0.0); diff --git a/src/video/gl/gl33core_context.cpp b/src/video/gl/gl33core_context.cpp index cfd63612b4..ca4d242beb 100644 --- a/src/video/gl/gl33core_context.cpp +++ b/src/video/gl/gl33core_context.cpp @@ -99,8 +99,8 @@ GL33CoreContext::bind() const float matrix[3*3] = { sx, 0.0, 0, - 0.0, -sy, 0, - tx, 1.0f - ty, 1.0, + 0.0, sy, 0, + tx, ty, 1.0, }; glUniformMatrix3fv(m_program->get_fragcoord2uv_location(), 1, false, matrix); From 393614f19ec0ad8a71f46011efbca438d5a0f845 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 28 Aug 2025 21:34:45 -0400 Subject: [PATCH 123/141] Notification: Don't calculate mini text height if there's no mini text --- src/gui/notification.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index 68a750358a..0293a78dea 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -89,8 +89,9 @@ Notification::set_mini_text(const std::string& text) void Notification::calculate_size() { + float mini_text_height = m_mini_text.empty() ? 0.f : m_mini_text_size.height + 24.f; m_size = Sizef(std::max(m_text_size.width, m_mini_text_size.width) + 60.0f, - m_text_size.height + m_mini_text_size.height + 40.0f); + m_text_size.height + mini_text_height + 16.f); } void From 956c73b8a7e6091c55f3305cb499663db94cf703 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 28 Aug 2025 22:18:08 -0400 Subject: [PATCH 124/141] Notification: Drag to close --- src/gui/notification.cpp | 46 ++++++++++++++++++++++++++++++++++++---- src/gui/notification.hpp | 4 ++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index 0293a78dea..1c5287f26e 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -30,6 +30,10 @@ #include "video/viewport.hpp" #include "util/gettext.hpp" #include "util/log.hpp" +#include + +constexpr float DRAG_DEADZONE = 10.f; +constexpr float DRAG_MAX = 120.f; Notification::Notification(const std::string& id, float idle_close_time, bool no_auto_close, bool auto_disable) : @@ -43,7 +47,9 @@ Notification::Notification(const std::string& id, float idle_close_time, m_mini_text(), m_text_size(), m_mini_text_size(), + m_init_mouse_click(0), m_pos(), + m_drag(), m_size(), m_mouse_pos(), m_mouse_over(false), @@ -119,8 +125,11 @@ Notification::draw(DrawingContext& context) m_pos = Vector(context.get_width() - std::max(m_text_size.width, m_mini_text_size.width) - 90.0f, static_cast(context.get_height() / 12) - m_text_size.height - m_mini_text_size.height + 10.0f); + m_pos.x -= m_drag.x; + float visibility = std::clamp(1.2f - (m_drag.x * 0.01f), 0.0f, 1.0f); + context.set_alpha(visibility); Rectf bg_rect(m_pos, m_size); - + // Draw background rect context.color().draw_filled_rect(bg_rect.grown(12.0f), Color(g_config->menubackcolor.red, g_config->menubackcolor.green, @@ -183,6 +192,12 @@ Notification::draw(DrawingContext& context) context.pop_transform(); } +Vector +Notification::drag_amount(const SDL_Event& ev) +{ + return m_init_mouse_click - VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); +} + void Notification::event(const SDL_Event& ev) { @@ -207,13 +222,31 @@ Notification::event(const SDL_Event& ev) } else // Notification clicked (execute callback) { - m_callback(); - if (m_auto_disable) disable(); - if (m_auto_close) close(); + if (m_init_mouse_click.x == 0 && m_init_mouse_click.y == 0) + m_init_mouse_click = VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); } } } break; + + case SDL_MOUSEBUTTONUP: + if (ev.button.button == SDL_BUTTON_LEFT) + { + m_init_mouse_click -= VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); + if (std::abs(m_init_mouse_click.x) < DRAG_DEADZONE) + { + m_callback(); + if (m_auto_disable) disable(); + if (m_auto_close) close(); + } + else if (std::abs(m_init_mouse_click.x) > DRAG_MAX) + { + close(); + } + m_init_mouse_click.x = m_init_mouse_click.y = 0; + m_drag.x = m_drag.y = 0; + } + break; case SDL_MOUSEMOTION: { @@ -229,6 +262,11 @@ Notification::event(const SDL_Event& ev) { m_idle_close_timer.start(m_idle_close_time); } + + if (m_init_mouse_click.x != 0 && m_init_mouse_click.y != 0) + { + m_drag = drag_amount(ev); + } if (MouseCursor::current() && m_mouse_over) MouseCursor::current()->set_state(MouseCursorState::LINK); diff --git a/src/gui/notification.hpp b/src/gui/notification.hpp index 5e20997e66..732f43410a 100644 --- a/src/gui/notification.hpp +++ b/src/gui/notification.hpp @@ -44,7 +44,10 @@ class Notification Vector m_pos; Sizef m_size; + Vector m_init_mouse_click; Vector m_mouse_pos; + Vector m_drag; + bool m_dragging; bool m_mouse_over; bool m_mouse_over_sym1; // Mouse is over "Do not show again". bool m_mouse_over_sym2; // Mouse is over "Close". @@ -75,6 +78,7 @@ class Notification private: void calculate_size(); + Vector drag_amount(const SDL_Event& ev); private: Notification(const Notification&) = delete; From 020cfbe8800223d346ea8ec39b18c3ecbcdaad7c Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 28 Aug 2025 22:46:50 -0400 Subject: [PATCH 125/141] Notification: Close animation --- src/gui/notification.cpp | 32 ++++++++++++++++++++++++++++---- src/gui/notification.hpp | 4 ++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index 1c5287f26e..75319a878c 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -49,8 +49,10 @@ Notification::Notification(const std::string& id, float idle_close_time, m_mini_text_size(), m_init_mouse_click(0), m_pos(), + m_vel(), m_drag(), m_size(), + m_closing(false), m_mouse_pos(), m_mouse_over(false), m_mouse_over_sym1(false), @@ -119,6 +121,27 @@ Notification::draw(DrawingContext& context) return; } } + + if (!m_mouse_down && !m_closing) + { + m_drag = m_drag * 0.8; + } + + if (m_closing) + { + m_vel -= 0.8; + m_drag += m_vel; + } + std::cout << m_drag << std::endl; + + if (m_closing && (m_drag.x < -400 || m_drag.x > 0)) + { + if (MouseCursor::current() && m_mouse_over) + MouseCursor::current()->set_state(MouseCursorState::NORMAL); + + MenuManager::instance().set_notification({}); + } + context.push_transform(); context.set_alpha(m_alpha); @@ -223,7 +246,10 @@ Notification::event(const SDL_Event& ev) else // Notification clicked (execute callback) { if (m_init_mouse_click.x == 0 && m_init_mouse_click.y == 0) + { m_init_mouse_click = VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); + m_mouse_down = true; + } } } } @@ -244,7 +270,7 @@ Notification::event(const SDL_Event& ev) close(); } m_init_mouse_click.x = m_init_mouse_click.y = 0; - m_drag.x = m_drag.y = 0; + m_mouse_down = false; } break; @@ -310,9 +336,7 @@ Notification::disable() void Notification::close() { - if (MouseCursor::current() && m_mouse_over) - MouseCursor::current()->set_state(MouseCursorState::NORMAL); - MenuManager::instance().set_notification({}); + m_closing = true; } // Static functions, serving as utilities diff --git a/src/gui/notification.hpp b/src/gui/notification.hpp index 732f43410a..765c78ddce 100644 --- a/src/gui/notification.hpp +++ b/src/gui/notification.hpp @@ -47,10 +47,14 @@ class Notification Vector m_init_mouse_click; Vector m_mouse_pos; Vector m_drag; + float m_vel; bool m_dragging; + bool m_mouse_down; bool m_mouse_over; bool m_mouse_over_sym1; // Mouse is over "Do not show again". bool m_mouse_over_sym2; // Mouse is over "Close". + + bool m_closing; bool m_quit; // Requested notification quit. From 588a85f60644e4355d6bc89caf948c3ebfb5d1db Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Thu, 28 Aug 2025 22:52:05 -0400 Subject: [PATCH 126/141] Notification: Remove debug print & print disabled notification as debug --- src/gui/notification.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index 75319a878c..5d525c4b1d 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -62,7 +62,7 @@ Notification::Notification(const std::string& id, float idle_close_time, { if (is_disabled(id)) // The notification exists in the config as disabled. { - log_warning << "Requested launch of disabled notification with ID \"" << m_id << "\". Closing." << std::endl; + log_debug << "Requested launch of disabled notification with ID \"" << m_id << "\". Closing." << std::endl; m_quit = true; return; } @@ -118,7 +118,6 @@ Notification::draw(DrawingContext& context) if (m_alpha <= 0.f) { close(); - return; } } @@ -132,7 +131,6 @@ Notification::draw(DrawingContext& context) m_vel -= 0.8; m_drag += m_vel; } - std::cout << m_drag << std::endl; if (m_closing && (m_drag.x < -400 || m_drag.x > 0)) { From b46bf521a93645f8bea6ba0e796e7032b5338a54 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 29 Aug 2025 01:29:06 -0400 Subject: [PATCH 127/141] Make toolbar prettier --- src/editor/button_widget.cpp | 6 ++-- src/editor/button_widget.hpp | 4 +++ src/editor/editor.cpp | 60 ++++++++++++++++++++++++++---------- src/editor/editor.hpp | 3 ++ 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/editor/button_widget.cpp b/src/editor/button_widget.cpp index 7f7d538edf..cead45b519 100644 --- a/src/editor/button_widget.cpp +++ b/src/editor/button_widget.cpp @@ -34,6 +34,7 @@ ButtonWidget::ButtonWidget(SpritePtr sprite, const Vector& pos, m_sig_click(std::move(sig_click)), m_mouse_pos(), m_help_text(), + m_flat(false), m_disabled(false) { } @@ -53,8 +54,9 @@ ButtonWidget::set_position(const Vector& pos) void ButtonWidget::draw(DrawingContext& context) { - context.color().draw_filled_rect(m_rect, Color(0.0f, 0.0f, 0.0f, 0.6f), 4.0f, - LAYER_GUI-5); + if (!m_flat) + context.color().draw_filled_rect(m_rect, Color(0.0f, 0.0f, 0.0f, 0.6f), 4.0f, + LAYER_GUI-5); if (m_sprite) { if (m_disabled) context.set_alpha(0.4); diff --git a/src/editor/button_widget.hpp b/src/editor/button_widget.hpp index c7f58cea2c..b33e79ff9d 100644 --- a/src/editor/button_widget.hpp +++ b/src/editor/button_widget.hpp @@ -52,6 +52,9 @@ class ButtonWidget : public Widget inline void set_disabled(bool disabled) { m_disabled = disabled; } inline bool is_disabled() { return m_disabled; } + + inline void set_flat(bool flat) { m_flat = flat; } + inline bool is_flat() { return m_flat; } protected: SpritePtr m_sprite; @@ -59,6 +62,7 @@ class ButtonWidget : public Widget bool m_grab; bool m_hover; bool m_disabled; + bool m_flat; std::function m_sig_click; Vector m_mouse_pos; std::string m_help_text; diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 46b75b5d0b..ce0a77a956 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -16,6 +16,7 @@ #include "editor/editor.hpp" #include "gui/notification.hpp" +#include "math/rectf.hpp" #include #include @@ -145,6 +146,8 @@ Editor::Editor() : m_tileset(nullptr), m_has_deprecated_tiles(false), m_widgets(), + m_widgets_width(), + m_widgets_width_offset(), m_controls(), m_undo_widget(), m_redo_widget(), @@ -295,6 +298,7 @@ Editor::Editor() : { Vector pos(32 * (i-2), 0); widget->set_position(pos); + widget->set_flat(true); m_widgets.insert(m_widgets.begin() + i, std::move(widget)); ++i; } @@ -303,6 +307,7 @@ Editor::Editor() : { Vector pos(32 * (i-2), 0); widget->set_position(pos); + widget->set_flat(true); widget->set_visible_in_object_mode(false); widget->set_visible(false); m_widgets.insert(m_widgets.begin() + i, std::move(widget)); @@ -313,11 +318,13 @@ Editor::Editor() : { Vector pos(32 * (i-tile_mode_widgets.size()-2), 0); widget->set_position(pos); + widget->set_flat(true); widget->set_visible_in_tile_mode(false); widget->set_visible(false); m_widgets.insert(m_widgets.begin() + i, std::move(widget)); ++i; } + m_widgets_width = 32.f * (i-std::max(tile_mode_widgets.size(), object_mode_widgets.size())-2-2); m_undo_widget = reinterpret_cast(m_widgets[2].get()); m_redo_widget = reinterpret_cast(m_widgets[3].get()); @@ -369,6 +376,14 @@ Editor::draw(Compositor& compositor) if (m_levelloaded) { + context.color().set_blur(g_config->editor_blur); + context.color().draw_filled_rect( + {-g_config->menuroundness, -g_config->menuroundness, m_widgets_width + m_widgets_width_offset, 32}, + Color(0.2f, 0.2f, 0.2f, 0.5f), + math::clamp(g_config->menuroundness, 0.f, 16.f), + LAYER_GUI - 5); + context.color().set_blur(0); + for(const auto& widget : m_widgets) { if (!g_config->editor_show_toolbar_widgets && @@ -1370,30 +1385,43 @@ Editor::event(const SDL_Event& ev) void Editor::toggle_tile_object_mode() { + int i = 0, total = 0; auto& tilebox = m_toolbox_widget->get_tilebox(); const auto& input_type = tilebox.get_input_type(); + if (input_type == InputType::OBJECT) { - select_last_tilegroup(); - for(const auto& widget : m_widgets) - { - if (auto toolbar_button = dynamic_cast(widget.get())) - { - toolbar_button->set_visible(toolbar_button->get_visible_in_tile_mode()); - } - } + select_last_tilegroup(); + for(const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + toolbar_button->set_visible(toolbar_button->get_visible_in_tile_mode()); + } + } } else { - select_last_objectgroup(); - for(const auto& widget : m_widgets) - { - if (auto toolbar_button = dynamic_cast(widget.get())) - { - toolbar_button->set_visible(toolbar_button->get_visible_in_object_mode()); - } - } + select_last_objectgroup(); + for(const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + toolbar_button->set_visible(toolbar_button->get_visible_in_object_mode()); + } + } } + + for (const auto& widget : m_widgets) + { + if (auto toolbar_button = dynamic_cast(widget.get())) + { + if (toolbar_button->get_visible()) + ++i; + } + } + + m_widgets_width = i * 32.f; } diff --git a/src/editor/editor.hpp b/src/editor/editor.hpp index 1fa9e92d6d..795dfc04b8 100644 --- a/src/editor/editor.hpp +++ b/src/editor/editor.hpp @@ -277,6 +277,9 @@ class Editor final : public Screen, float m_scroll_speed; float m_new_scale; + + float m_widgets_width; + float m_widgets_width_offset; Vector m_mouse_pos; From c2ac52eb750a6a0b7871b418f065d66fcfd950fb Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 29 Aug 2025 02:51:14 -0400 Subject: [PATCH 128/141] Notification: Open animation --- src/gui/notification.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index 5d525c4b1d..bc653c30d7 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -100,6 +100,7 @@ Notification::calculate_size() float mini_text_height = m_mini_text.empty() ? 0.f : m_mini_text_size.height + 24.f; m_size = Sizef(std::max(m_text_size.width, m_mini_text_size.width) + 60.0f, m_text_size.height + mini_text_height + 16.f); + m_drag.x -= m_size.width + 100.f; } void From 90e01487797f0ebdf89aa72b7e9d1510d559d387 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 29 Aug 2025 03:03:08 -0400 Subject: [PATCH 129/141] Fix notifications --- src/supertux/main.cpp | 2 +- src/supertux/menu/editor_temp_save_as.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/supertux/main.cpp b/src/supertux/main.cpp index 4ca182515d..a69b69fe6f 100644 --- a/src/supertux/main.cpp +++ b/src/supertux/main.cpp @@ -799,7 +799,7 @@ Main::release_check() const std::string version = version_full.substr(version_full.find("v") + 1, version_full.find("-") - 1); if (version != latest_ver) { - auto notif = std::make_unique("new_release_" + latest_ver, 20.f, false, true); + auto notif = std::make_unique("new_release_" + latest_ver, 20.f); notif->set_text(fmt::format(fmt::runtime(_("New release: SuperTux v{}!")), latest_ver)); notif->set_mini_text(_("Click for more details.")); notif->on_press([latest_ver]() diff --git a/src/supertux/menu/editor_temp_save_as.cpp b/src/supertux/menu/editor_temp_save_as.cpp index 5dc7cc7f4e..f8d2ad453b 100644 --- a/src/supertux/menu/editor_temp_save_as.cpp +++ b/src/supertux/menu/editor_temp_save_as.cpp @@ -86,7 +86,7 @@ EditorTempSaveAs::menu_action(MenuItem& item) editor->set_world(std::move(std::unique_ptr(m_world.release()))); - auto notif = std::make_unique("create_level_notif", 5.f); + auto notif = std::make_unique("create_level_notif", 5.f, false, true); notif->set_text(_("Level created!")); MenuManager::instance().set_notification(std::move(notif)); From 64e3c20d66681f6324a04fcad3df6442710fb6d0 Mon Sep 17 00:00:00 2001 From: Swagtoy Date: Fri, 29 Aug 2025 03:13:36 -0400 Subject: [PATCH 130/141] Fix notifications being activated without being hovered --- src/gui/notification.cpp | 19 +++++++++++-------- src/supertux/main.cpp | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index bc653c30d7..98553c78bd 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -258,15 +258,18 @@ Notification::event(const SDL_Event& ev) if (ev.button.button == SDL_BUTTON_LEFT) { m_init_mouse_click -= VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); - if (std::abs(m_init_mouse_click.x) < DRAG_DEADZONE) - { - m_callback(); - if (m_auto_disable) disable(); - if (m_auto_close) close(); - } - else if (std::abs(m_init_mouse_click.x) > DRAG_MAX) + if (m_mouse_over) { - close(); + if (std::abs(m_init_mouse_click.x) < DRAG_DEADZONE) + { + m_callback(); + if (m_auto_disable) disable(); + if (m_auto_close) close(); + } + else if (std::abs(m_init_mouse_click.x) > DRAG_MAX) + { + close(); + } } m_init_mouse_click.x = m_init_mouse_click.y = 0; m_mouse_down = false; diff --git a/src/supertux/main.cpp b/src/supertux/main.cpp index a69b69fe6f..4ca182515d 100644 --- a/src/supertux/main.cpp +++ b/src/supertux/main.cpp @@ -799,7 +799,7 @@ Main::release_check() const std::string version = version_full.substr(version_full.find("v") + 1, version_full.find("-") - 1); if (version != latest_ver) { - auto notif = std::make_unique("new_release_" + latest_ver, 20.f); + auto notif = std::make_unique("new_release_" + latest_ver, 20.f, false, true); notif->set_text(fmt::format(fmt::runtime(_("New release: SuperTux v{}!")), latest_ver)); notif->set_mini_text(_("Click for more details.")); notif->on_press([latest_ver]() From a499684207f086e32b145e2304bf9375f03d71ab Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Sat, 30 Aug 2025 18:00:27 +0300 Subject: [PATCH 131/141] Slight code improvement in notification.cpp [ci skip] --- src/gui/notification.cpp | 38 ++++++++++++++++---------------------- src/gui/notification.hpp | 5 +++-- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/gui/notification.cpp b/src/gui/notification.cpp index 98553c78bd..c8d367eead 100644 --- a/src/gui/notification.cpp +++ b/src/gui/notification.cpp @@ -108,40 +108,34 @@ Notification::draw(DrawingContext& context) { // Close notification, if a quit has been requested, or neither the MenuManager or Editor aren't active. if (m_quit || !(MenuManager::instance().is_active() || Editor::is_active())) - { close(); - return; - } if (m_alpha < 1.f || m_idle_close_timer.check()) { m_alpha -= 0.01f; if (m_alpha <= 0.f) - { close(); - } - } - - if (!m_mouse_down && !m_closing) - { - m_drag = m_drag * 0.8; } if (m_closing) { m_vel -= 0.8; m_drag += m_vel; + + if (m_drag.x < -400 || m_drag.x > 0) + { + if (MouseCursor::current() && m_mouse_over) + MouseCursor::current()->set_state(MouseCursorState::NORMAL); + + MenuManager::instance().set_notification({}); + return; + } } - - if (m_closing && (m_drag.x < -400 || m_drag.x > 0)) + else if (!m_mouse_down) { - if (MouseCursor::current() && m_mouse_over) - MouseCursor::current()->set_state(MouseCursorState::NORMAL); - - MenuManager::instance().set_notification({}); + m_drag = m_drag * 0.8; } - context.push_transform(); context.set_alpha(m_alpha); @@ -151,7 +145,7 @@ Notification::draw(DrawingContext& context) float visibility = std::clamp(1.2f - (m_drag.x * 0.01f), 0.0f, 1.0f); context.set_alpha(visibility); Rectf bg_rect(m_pos, m_size); - + // Draw background rect context.color().draw_filled_rect(bg_rect.grown(12.0f), Color(g_config->menubackcolor.red, g_config->menubackcolor.green, @@ -215,7 +209,7 @@ Notification::draw(DrawingContext& context) } Vector -Notification::drag_amount(const SDL_Event& ev) +Notification::drag_amount(const SDL_Event& ev) const { return m_init_mouse_click - VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); } @@ -244,7 +238,7 @@ Notification::event(const SDL_Event& ev) } else // Notification clicked (execute callback) { - if (m_init_mouse_click.x == 0 && m_init_mouse_click.y == 0) + if (m_init_mouse_click.x == 0 && m_init_mouse_click.y == 0) { m_init_mouse_click = VideoSystem::current()->get_viewport().to_logical(ev.button.x, ev.button.y); m_mouse_down = true; @@ -253,7 +247,7 @@ Notification::event(const SDL_Event& ev) } } break; - + case SDL_MOUSEBUTTONUP: if (ev.button.button == SDL_BUTTON_LEFT) { @@ -290,7 +284,7 @@ Notification::event(const SDL_Event& ev) { m_idle_close_timer.start(m_idle_close_time); } - + if (m_init_mouse_click.x != 0 && m_init_mouse_click.y != 0) { m_drag = drag_amount(ev); diff --git a/src/gui/notification.hpp b/src/gui/notification.hpp index 765c78ddce..6d4e5c7e4a 100644 --- a/src/gui/notification.hpp +++ b/src/gui/notification.hpp @@ -53,7 +53,7 @@ class Notification bool m_mouse_over; bool m_mouse_over_sym1; // Mouse is over "Do not show again". bool m_mouse_over_sym2; // Mouse is over "Close". - + bool m_closing; bool m_quit; // Requested notification quit. @@ -82,7 +82,8 @@ class Notification private: void calculate_size(); - Vector drag_amount(const SDL_Event& ev); + + Vector drag_amount(const SDL_Event& ev) const; private: Notification(const Notification&) = delete; From f5779097606b6284adc0053ff8eda301ee7207d5 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 1 Sep 2025 02:31:40 +0200 Subject: [PATCH 132/141] Return added options from all add_* calls in ObjectSettings class --- src/editor/object_settings.cpp | 147 +++++++++++++++++---------------- src/editor/object_settings.hpp | 72 ++++++++-------- 2 files changed, 110 insertions(+), 109 deletions(-) diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index c994c3697c..203786e3a0 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -50,7 +50,7 @@ ObjectSettings::ObjectSettings(ObjectSettings* obj) : // } } -void +std::unique_ptr& ObjectSettings::add_option(std::unique_ptr option) { if (!option->get_key().empty()) @@ -64,19 +64,21 @@ ObjectSettings::add_option(std::unique_ptr option) } m_options.push_back(std::move(option)); + + return m_options.back(); } -void +std::unique_ptr& ObjectSettings::add_objects(const std::string& text, std::vector>* value_ptr, uint8_t get_objects_param, const std::function)>& add_object_func, const std::string& key, unsigned int flags) { auto select_option = std::make_unique(text, value_ptr, get_objects_param, add_object_func, key, flags); select_option->set_description("Select option (should be described in more detail here)"); - add_option(std::move(select_option)); + return add_option(std::move(select_option)); } -void +std::unique_ptr& ObjectSettings::add_color(const std::string& text, Color* value_ptr, const std::string& key, const std::optional& default_value, @@ -84,10 +86,10 @@ ObjectSettings::add_color(const std::string& text, Color* value_ptr, { auto color_option = std::make_unique(text, value_ptr, key, default_value, true, flags); color_option->set_description("Color option (should be described in more detail here)"); - add_option(std::move(color_option)); + return add_option(std::move(color_option)); } -void +std::unique_ptr& ObjectSettings::add_rgba(const std::string& text, Color* value_ptr, const std::string& key, const std::optional& default_value, @@ -95,10 +97,10 @@ ObjectSettings::add_rgba(const std::string& text, Color* value_ptr, { auto color_object_option = std::make_unique(text, value_ptr, key, default_value, true, flags); color_object_option->set_description("Color option (should be described in more detail here)"); - add_option(std::move(color_object_option)); + return add_option(std::move(color_object_option)); } -void +std::unique_ptr& ObjectSettings::add_rgb(const std::string& text, Color* value_ptr, const std::string& key, const std::optional& default_value, @@ -106,10 +108,10 @@ ObjectSettings::add_rgb(const std::string& text, Color* value_ptr, { auto rgb_option = std::make_unique(text, value_ptr, key, default_value, false, flags); rgb_option->set_description("Color option (should be described in more detail here)"); - add_option(std::move(rgb_option)); + return add_option(std::move(rgb_option)); } -void +std::unique_ptr& ObjectSettings::add_bool(const std::string& text, bool* value_ptr, const std::string& key, const std::optional& default_value, @@ -117,10 +119,10 @@ ObjectSettings::add_bool(const std::string& text, bool* value_ptr, { auto bool_option = std::make_unique(text, value_ptr, key, default_value, flags); bool_option->set_description("Bool option (should be described in more detail here)"); - add_option(std::move(bool_option)); + return add_option(std::move(bool_option)); } -void +std::unique_ptr& ObjectSettings::add_float(const std::string& text, float* value_ptr, const std::string& key, const std::optional& default_value, @@ -128,10 +130,10 @@ ObjectSettings::add_float(const std::string& text, float* value_ptr, { auto float_option = std::make_unique(text, value_ptr, key, default_value, flags); float_option->set_description("Float option (should be described in more detail here)"); - add_option(std::move(float_option)); + return add_option(std::move(float_option)); } -void +std::unique_ptr& ObjectSettings::add_int(const std::string& text, int* value_ptr, const std::string& key, const std::optional& default_value, @@ -139,118 +141,118 @@ ObjectSettings::add_int(const std::string& text, int* value_ptr, { auto int_option = std::make_unique(text, value_ptr, key, default_value, flags); int_option->set_description("Int option (should be described in more detail here)"); - add_option(std::move(int_option)); + return add_option(std::move(int_option)); } -void +std::unique_ptr& ObjectSettings::add_label(const std::string& text, unsigned int flags) { - add_option(std::make_unique(text, flags)); + return add_option(std::make_unique(text, flags)); } -void +std::unique_ptr& ObjectSettings::add_direction(const std::string& text, Direction* value_ptr, std::vector possible_directions, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, std::move(possible_directions), - key, flags)); + return add_option(std::make_unique(text, value_ptr, std::move(possible_directions), + key, flags)); } -void +std::unique_ptr& ObjectSettings::add_worldmap_direction(const std::string& text, worldmap::Direction* value_ptr, std::optional default_value, const std::string& key, unsigned int flags) { - add_enum(text, reinterpret_cast(value_ptr), + return add_enum(text, reinterpret_cast(value_ptr), {_("None"), _("West"), _("East"), _("North"), _("South")}, {"none", "west", "east", "north", "south"}, default_value ? static_cast(*default_value) : std::optional(), key, flags); } -void +std::unique_ptr& ObjectSettings::add_walk_mode(const std::string& text, WalkMode* value_ptr, const std::optional& default_value, const std::string& key, unsigned int flags) { - add_option(std::make_unique( + return add_option(std::make_unique( text, reinterpret_cast(value_ptr), std::vector{_("One shot"), _("Ping-pong"), _("Circular")}, std::nullopt, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_remove() { - add_option(std::make_unique()); + return add_option(std::make_unique()); } -void +std::unique_ptr& ObjectSettings::add_script(UID uid, const std::string& text, std::string* value_ptr, const std::string& key, unsigned int flags) { - add_option(std::make_unique(uid, text, value_ptr, key, flags)); + return add_option(std::make_unique(uid, text, value_ptr, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_text(const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, flags)); + return add_option(std::make_unique(text, value_ptr, key, default_value, flags)); } -void +std::unique_ptr& ObjectSettings::add_translatable_text(const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, default_value, + return add_option(std::make_unique(text, value_ptr, key, default_value, flags | OPTION_TRANSLATABLE)); } -void +std::unique_ptr& ObjectSettings::add_multiline_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(uid, text, value_ptr, key, default_value, flags)); + return add_option(std::make_unique(uid, text, value_ptr, key, default_value, flags)); } -void +std::unique_ptr& ObjectSettings::add_multiline_translatable_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, unsigned int flags) { - add_option(std::make_unique(uid, text, value_ptr, key, default_value, + return add_option(std::make_unique(uid, text, value_ptr, key, default_value, flags | OPTION_TRANSLATABLE)); } -void +std::unique_ptr& ObjectSettings::add_string_select(const std::string& text, int* value_ptr, const std::vector& select, const std::optional& default_value, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, select, default_value, key, flags)); + return add_option(std::make_unique(text, value_ptr, select, default_value, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_enum(const std::string& text, int* value_ptr, const std::vector& labels, const std::vector& symbols, const std::optional& default_value, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, labels, symbols, default_value, key, flags)); + return add_option(std::make_unique(text, value_ptr, labels, symbols, default_value, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_file(const std::string& text, std::string* value_ptr, const std::string& key, const std::optional& default_value, @@ -259,29 +261,27 @@ ObjectSettings::add_file(const std::string& text, std::string* value_ptr, bool path_relative_to_basedir, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, default_value, key, filter, basedir, path_relative_to_basedir, flags)); + return add_option(std::make_unique(text, value_ptr, default_value, key, filter, basedir, path_relative_to_basedir, flags)); } -void +std::unique_ptr& ObjectSettings::add_tiles(const std::string& text, TileMap* value_ptr, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, value_ptr, key, flags)); + return add_option(std::make_unique(text, value_ptr, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_path(const std::string& text, Path* path, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, path, key, flags)); + return add_option(std::make_unique(text, path, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_path_ref(const std::string& text, PathObject& target, const std::string& path_ref, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, target, path_ref, key, flags)); - if (!path_ref.empty()) { m_options.erase(std::remove_if(m_options.begin(), m_options.end(), [](const std::unique_ptr& obj) { @@ -289,97 +289,98 @@ ObjectSettings::add_path_ref(const std::string& text, PathObject& target, const }), m_options.end()); } + return add_option(std::make_unique(text, target, path_ref, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_level(const std::string& text, std::string* value_ptr, const std::string& key, const std::string& basedir, unsigned int flags) { - add_file(text, value_ptr, key, {}, {".stl"}, basedir, true, flags); + return add_file(text, value_ptr, key, {}, {".stl"}, basedir, true, flags); } -void +std::unique_ptr& ObjectSettings::add_sprite(const std::string& text, std::string* value_ptr, const std::string& key, std::optional default_value, unsigned int flags) { - add_file(text, value_ptr, key, std::move(default_value), {".jpg", ".png", ".sprite"}, {}, true, flags); + return add_file(text, value_ptr, key, std::move(default_value), {".jpg", ".png", ".sprite"}, {}, true, flags); } -void +std::unique_ptr& ObjectSettings::add_surface(const std::string& text, std::string* value_ptr, const std::string& key, std::optional default_value, unsigned int flags) { - add_file(text, value_ptr, key, std::move(default_value), {".jpg", ".png", ".surface"}, {}, true, flags); + return add_file(text, value_ptr, key, std::move(default_value), {".jpg", ".png", ".surface"}, {}, true, flags); } -void +std::unique_ptr& ObjectSettings::add_sound(const std::string& text, std::string* value_ptr, const std::string& key, std::optional default_value, unsigned int flags) { - add_file(text, value_ptr, key, std::move(default_value), {".wav", ".ogg"}, {}, true, flags); + return add_file(text, value_ptr, key, std::move(default_value), {".wav", ".ogg"}, {}, true, flags); } -void +std::unique_ptr& ObjectSettings::add_music(const std::string& text, std::string* value_ptr, const std::string& key, std::optional default_value, unsigned int flags) { - add_file(text, value_ptr, key, std::move(default_value), {".music"}, {"/music"}, false, flags); + return add_file(text, value_ptr, key, std::move(default_value), {".music"}, {"/music"}, false, flags); } -void +std::unique_ptr& ObjectSettings::add_worldmap(const std::string& text, std::string* value_ptr, const std::string& key, unsigned int flags) { - add_file(text, value_ptr, key, {}, {".stwm"}, {}, true, flags); + return add_file(text, value_ptr, key, {}, {".stwm"}, {}, true, flags); } -void +std::unique_ptr& ObjectSettings::add_sexp(const std::string& text, const std::string& key, sexp::Value& value, unsigned int flags) { - add_option(std::make_unique(text, key, value, flags)); + return add_option(std::make_unique(text, key, value, flags)); } -void +std::unique_ptr& ObjectSettings::add_string_array(const std::string& text, const std::string& key, std::vector& items) { - add_option(std::make_unique(text, key, items)); + return add_option(std::make_unique(text, key, items)); } -void +std::unique_ptr& ObjectSettings::add_test_from_here(const MovingObject* object_ptr) { - add_option(std::make_unique(object_ptr)); + return add_option(std::make_unique(object_ptr)); } -void +std::unique_ptr& ObjectSettings::add_particle_editor() { - add_option(std::make_unique()); + return add_option(std::make_unique()); } -void +std::unique_ptr& ObjectSettings::add_path_handle(const std::string& text, PathWalker::Handle& handle, const std::string& key, unsigned int flags) { - add_option(std::make_unique(text, handle, key, flags)); + return add_option(std::make_unique(text, handle, key, flags)); } -void +std::unique_ptr& ObjectSettings::add_list(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr) { - add_option(std::make_unique(text, key, items, value_ptr)); + return add_option(std::make_unique(text, key, items, value_ptr)); } void diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index be566fc305..5b85c75785 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -51,113 +51,113 @@ class ObjectSettings final inline const std::string& get_name() const { return m_name; } - void add_bool(const std::string& text, bool* value_ptr, + std::unique_ptr& add_bool(const std::string& text, bool* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_float(const std::string& text, float* value_ptr, + std::unique_ptr& add_float(const std::string& text, float* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_int(const std::string& text, int* value_ptr, + std::unique_ptr& add_int(const std::string& text, int* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_label(const std::string& text, unsigned int flags = 0); - void add_worldmap_direction(const std::string& text, worldmap::Direction* value_ptr, + std::unique_ptr& add_label(const std::string& text, unsigned int flags = 0); + std::unique_ptr& add_worldmap_direction(const std::string& text, worldmap::Direction* value_ptr, std::optional default_value = {}, const std::string& key = {}, unsigned int flags = 0); - void add_direction(const std::string& text, Direction* value_ptr, + std::unique_ptr& add_direction(const std::string& text, Direction* value_ptr, std::vector possible_directions = {}, const std::string& key = {}, unsigned int flags = 0); - void add_walk_mode(const std::string& text, WalkMode* value_ptr, + std::unique_ptr& add_walk_mode(const std::string& text, WalkMode* value_ptr, const std::optional& default_value = {}, const std::string& key = {}, unsigned int flags = 0); - void add_objects(const std::string& text, std::vector>* value_ptr, + std::unique_ptr& add_objects(const std::string& text, std::vector>* value_ptr, uint8_t get_objects_param = 0, const std::function)>& add_object_func = {}, const std::string& key = {}, unsigned int flags = 0); - void add_color(const std::string& text, Color* value_ptr, + std::unique_ptr& add_color(const std::string& text, Color* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_rgba(const std::string& text, Color* value_ptr, + std::unique_ptr& add_rgba(const std::string& text, Color* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_rgb(const std::string& text, Color* value_ptr, + std::unique_ptr& add_rgb(const std::string& text, Color* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_remove(); - void add_script(UID uid, const std::string& text, std::string* value_ptr, + std::unique_ptr& add_remove(); + std::unique_ptr& add_script(UID uid, const std::string& text, std::string* value_ptr, const std::string& key = {}, unsigned int flags = 0); - void add_text(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_text(const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_translatable_text(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_translatable_text(const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_multiline_text(UID uid, const std::string& text, std::string* value_ptr, + std::unique_ptr& add_multiline_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_multiline_translatable_text(UID uid, const std::string& text, std::string* value_ptr, + std::unique_ptr& add_multiline_translatable_text(UID uid, const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, unsigned int flags = 0); - void add_string_select(const std::string& text, int* value_ptr, const std::vector& select, + std::unique_ptr& add_string_select(const std::string& text, int* value_ptr, const std::vector& select, const std::optional& default_value = {}, const std::string& key = {}, unsigned int flags = 0); - void add_enum(const std::string& text, int* value_ptr, + std::unique_ptr& add_enum(const std::string& text, int* value_ptr, const std::vector& labels, const std::vector& symbols, const std::optional& default_value = {}, const std::string& key = {}, unsigned int flags = 0); - void add_sprite(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_sprite(const std::string& text, std::string* value_ptr, const std::string& key = {}, std::optional default_value = {}, unsigned int flags = 0); - void add_surface(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_surface(const std::string& text, std::string* value_ptr, const std::string& key = {}, std::optional default_value = {}, unsigned int flags = 0); - void add_sound(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_sound(const std::string& text, std::string* value_ptr, const std::string& key = {}, std::optional default_value = {}, unsigned int flags = 0); - void add_music(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_music(const std::string& text, std::string* value_ptr, const std::string& key = {}, std::optional default_value = {}, unsigned int flags = 0); - void add_worldmap(const std::string& text, std::string* value_ptr, const std::string& key = {}, + std::unique_ptr& add_worldmap(const std::string& text, std::string* value_ptr, const std::string& key = {}, unsigned int flags = 0); - void add_level(const std::string& text, std::string* value_ptr, const std::string& basedir, + std::unique_ptr& add_level(const std::string& text, std::string* value_ptr, const std::string& basedir, const std::string& key = {}, unsigned int flags = 0); - void add_tiles(const std::string& text, TileMap* value_ptr, const std::string& key = {}, + std::unique_ptr& add_tiles(const std::string& text, TileMap* value_ptr, const std::string& key = {}, unsigned int flags = 0); - void add_path(const std::string& text, Path* path, const std::string& key = {}, + std::unique_ptr& add_path(const std::string& text, Path* path, const std::string& key = {}, unsigned int flags = 0); - void add_path_ref(const std::string& text, PathObject& target, const std::string& path_ref, + std::unique_ptr& add_path_ref(const std::string& text, PathObject& target, const std::string& path_ref, const std::string& key = {}, unsigned int flags = 0); - void add_file(const std::string& text, std::string* value_ptr, + std::unique_ptr& add_file(const std::string& text, std::string* value_ptr, const std::string& key = {}, const std::optional& default_value = {}, const std::vector& filter = {}, const std::string& basedir = {}, bool path_relative_to_basedir = true, unsigned int flags = 0); - void add_sexp(const std::string& text, const std::string& key, + std::unique_ptr& add_sexp(const std::string& text, const std::string& key, sexp::Value& value, unsigned int flags = 0); - void add_string_array(const std::string& text, const std::string& key, std::vector& items); - void add_test_from_here(const MovingObject* object_ptr); - void add_particle_editor(); - void add_path_handle(const std::string& text, PathWalker::Handle& handle, + std::unique_ptr& add_string_array(const std::string& text, const std::string& key, std::vector& items); + std::unique_ptr& add_test_from_here(const MovingObject* object_ptr); + std::unique_ptr& add_particle_editor(); + std::unique_ptr& add_path_handle(const std::string& text, PathWalker::Handle& handle, const std::string& key = {}, unsigned int flags = 0); - void add_list(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr); + std::unique_ptr& add_list(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr); inline const std::vector>& get_options() const { return m_options; } @@ -185,7 +185,7 @@ class ObjectSettings final void save_new_state(Writer& writer) const; private: - void add_option(std::unique_ptr option); + std::unique_ptr& add_option(std::unique_ptr option); private: std::string m_name; From 36ff0c5a4cac1e625ec6f18d9d95cd5c0bfbe85b Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 1 Sep 2025 02:36:30 +0200 Subject: [PATCH 133/141] Remove placeholders --- src/editor/object_settings.cpp | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index 203786e3a0..832d39a684 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -73,9 +73,7 @@ ObjectSettings::add_objects(const std::string& text, std::vector)>& add_object_func, const std::string& key, unsigned int flags) { - auto select_option = std::make_unique(text, value_ptr, get_objects_param, add_object_func, key, flags); - select_option->set_description("Select option (should be described in more detail here)"); - return add_option(std::move(select_option)); + return add_option(std::make_unique(text, value_ptr, get_objects_param, add_object_func, key, flags)); } std::unique_ptr& @@ -84,9 +82,7 @@ ObjectSettings::add_color(const std::string& text, Color* value_ptr, const std::optional& default_value, unsigned int flags) { - auto color_option = std::make_unique(text, value_ptr, key, default_value, true, flags); - color_option->set_description("Color option (should be described in more detail here)"); - return add_option(std::move(color_option)); + return add_option(std::make_unique(text, value_ptr, key, default_value, true, flags)); } std::unique_ptr& @@ -95,9 +91,7 @@ ObjectSettings::add_rgba(const std::string& text, Color* value_ptr, const std::optional& default_value, unsigned int flags) { - auto color_object_option = std::make_unique(text, value_ptr, key, default_value, true, flags); - color_object_option->set_description("Color option (should be described in more detail here)"); - return add_option(std::move(color_object_option)); + return add_option(std::make_unique(text, value_ptr, key, default_value, true, flags)); } std::unique_ptr& @@ -106,9 +100,7 @@ ObjectSettings::add_rgb(const std::string& text, Color* value_ptr, const std::optional& default_value, unsigned int flags) { - auto rgb_option = std::make_unique(text, value_ptr, key, default_value, false, flags); - rgb_option->set_description("Color option (should be described in more detail here)"); - return add_option(std::move(rgb_option)); + return add_option(std::make_unique(text, value_ptr, key, default_value, false, flags)); } std::unique_ptr& @@ -117,9 +109,7 @@ ObjectSettings::add_bool(const std::string& text, bool* value_ptr, const std::optional& default_value, unsigned int flags) { - auto bool_option = std::make_unique(text, value_ptr, key, default_value, flags); - bool_option->set_description("Bool option (should be described in more detail here)"); - return add_option(std::move(bool_option)); + return add_option(std::make_unique(text, value_ptr, key, default_value, flags)); } std::unique_ptr& @@ -128,9 +118,7 @@ ObjectSettings::add_float(const std::string& text, float* value_ptr, const std::optional& default_value, unsigned int flags) { - auto float_option = std::make_unique(text, value_ptr, key, default_value, flags); - float_option->set_description("Float option (should be described in more detail here)"); - return add_option(std::move(float_option)); + return add_option(std::make_unique(text, value_ptr, key, default_value, flags)); } std::unique_ptr& @@ -139,9 +127,7 @@ ObjectSettings::add_int(const std::string& text, int* value_ptr, const std::optional& default_value, unsigned int flags) { - auto int_option = std::make_unique(text, value_ptr, key, default_value, flags); - int_option->set_description("Int option (should be described in more detail here)"); - return add_option(std::move(int_option)); + return add_option(std::make_unique(text, value_ptr, key, default_value, flags)); } std::unique_ptr& From e713d15e59c6433553404faae51962c23efbf359 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 1 Sep 2025 02:41:24 +0200 Subject: [PATCH 134/141] Add example descriptions for darttrap --- src/badguy/darttrap.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/badguy/darttrap.cpp b/src/badguy/darttrap.cpp index 1d6480fb9f..7c08565632 100644 --- a/src/badguy/darttrap.cpp +++ b/src/badguy/darttrap.cpp @@ -152,11 +152,16 @@ DartTrap::get_settings() { ObjectSettings result = StickyBadguy::get_settings(); - result.add_float(_("Initial delay"), &m_initial_delay, "initial-delay"); - result.add_bool(_("Enabled"), &m_enabled, "enabled", true); - result.add_float(_("Fire delay"), &m_fire_delay, "fire-delay"); - result.add_int(_("Ammo"), &m_ammo, "ammo"); - result.add_sprite(_("Dart sprite"), &m_dart_sprite, "dart-sprite", "images/creatures/darttrap/granito/root_dart.sprite"); + result.add_float(_("Initial delay"), &m_initial_delay, "initial-delay") + ->set_description(_("Time until the first dart is fired after the trap is activated.")); + result.add_bool(_("Enabled"), &m_enabled, "enabled", true) + ->set_description(_("Whether the trap is enabled.")); + result.add_float(_("Fire delay"), &m_fire_delay, "fire-delay") + ->set_description(_("Time between consecutive darts.")); + result.add_int(_("Ammo"), &m_ammo, "ammo") + ->set_description(_("Number of darts the trap can fire.")); + result.add_sprite(_("Dart sprite"), &m_dart_sprite, "dart-sprite", "images/creatures/darttrap/granito/root_dart.sprite") + ->set_description(_("Sprite used for the dart.")); result.reorder({"initial-delay", "fire-delay", "ammo", "sticky", "direction", "x", "y", "dart-sprite"}); From 9713af30c7d415e2a43dadf3e65af3e48b461ee3 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 1 Sep 2025 03:16:12 +0200 Subject: [PATCH 135/141] Help texts for Badguy and MovingSprite settings --- src/badguy/badguy.cpp | 6 ++++-- src/object/moving_sprite.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/badguy/badguy.cpp b/src/badguy/badguy.cpp index 4f43165668..6007da83b1 100644 --- a/src/badguy/badguy.cpp +++ b/src/badguy/badguy.cpp @@ -1267,8 +1267,10 @@ BadGuy::get_settings() ObjectSettings result = MovingSprite::get_settings(); if (!get_allowed_directions().empty()) - result.add_direction(_("Direction"), &m_start_dir, get_allowed_directions(), "direction"); - result.add_script(get_uid(), _("Death script"), &m_dead_script, "dead-script"); + result.add_direction(_("Direction"), &m_start_dir, get_allowed_directions(), "direction") + ->set_description(_("The direction the badguy is facing at the start of the level. AUTO makes the badguy face towards the player when activated.")); + result.add_script(get_uid(), _("Death script"), &m_dead_script, "dead-script") + ->set_description(_("Script that is executed when the badguy dies.")); result.reorder({"direction", "sprite", "x", "y"}); diff --git a/src/object/moving_sprite.cpp b/src/object/moving_sprite.cpp index 7ae025e8c1..bd1edfabc3 100644 --- a/src/object/moving_sprite.cpp +++ b/src/object/moving_sprite.cpp @@ -198,8 +198,10 @@ MovingSprite::get_settings() { ObjectSettings result = MovingObject::get_settings(); - result.add_sprite(_("Sprite"), &m_sprite_name, "sprite", get_default_sprite_name()); - result.add_int(_("Z-pos"), &m_layer, "z-pos"); + result.add_sprite(_("Sprite"), &m_sprite_name, "sprite", get_default_sprite_name()) + ->set_description(_("The sprite file used for this object.")); + result.add_int(_("Z-pos"), &m_layer, "z-pos") + ->set_description(_("The layer this object is drawn on. Higher layers are drawn on top of lower layers.")); result.reorder({"sprite", "z-pos", "x", "y"}); From 7f5e14ae9f1e9b5477821ba77bb7312d7d85320a Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Wed, 3 Sep 2025 17:06:16 +0200 Subject: [PATCH 136/141] Experiment: layers box at the bottom left corner displaying the layers as well as their name --- src/editor/layers_widget.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/editor/layers_widget.cpp b/src/editor/layers_widget.cpp index 0ae5781d77..8e120a37c2 100644 --- a/src/editor/layers_widget.cpp +++ b/src/editor/layers_widget.cpp @@ -130,6 +130,11 @@ EditorLayersWidget::draw(DrawingContext& context) Vector(35.0f, static_cast(m_Ypos) + 5.0f), ALIGN_LEFT, LAYER_GUI, ColorScheme::Menu::default_color); + // LAYERS_BOX_EXPERIMENT_BEGIN + context.color().draw_filled_rect(Rectf(Vector(0, SCREEN_HEIGHT - 400), Vector(200, SCREEN_HEIGHT)), + g_config->editorhovercolor, 0.0f, LAYER_GUI - 10); + // LAYERS_BOX_EXPERIMENT_END + int pos = 0; for (const auto& layer_icon : m_layer_icons) { @@ -139,6 +144,23 @@ EditorLayersWidget::draw(DrawingContext& context) layer_icon->draw(context, get_layer_coords(pos)); else if ((pos + 1) * 35 >= m_scroll) layer_icon->draw(context, get_layer_coords(pos), 35 - (m_scroll - pos * 35)); + + // LAYERS_BOX_EXPERIMENT_BEGIN + auto layer_icon_position = Vector(0, SCREEN_HEIGHT - 400 + (pos * 30)); + + layer_icon->draw(context, layer_icon_position); + + auto layer_name = layer_icon->get_layer()->get_name(); + if (layer_name.empty()) + { + layer_name = + fmt::format(fmt::runtime(_("Unnamed {}")), layer_icon->get_layer()->get_display_name()); + } + context.color().draw_text(Resources::small_font, layer_name, + layer_icon_position + Vector(35, 10), FontAlignment::ALIGN_LEFT, + LAYER_GUI - 9); + // LAYERS_BOX_EXPERIMENT_END + pos++; } if (pos * 35 >= m_scroll) From d06640b6d01d2b2786a8181db2ab3d34ee60ed60 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 6 Sep 2025 23:03:35 +0200 Subject: [PATCH 137/141] Fix performance issue [ci skip] --- src/util/file_system.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/file_system.cpp b/src/util/file_system.cpp index 5fbfccb074..da9028441f 100644 --- a/src/util/file_system.cpp +++ b/src/util/file_system.cpp @@ -88,7 +88,7 @@ std::string strip_leading_dirs(std::string filename) { while (filename.size() > 0 && (filename[filename.size()-1] == '/' || filename[filename.size()-1] == '\\')) { - filename = filename.substr(0, filename.size()-1); + filename.pop_back(); } return filename; } @@ -108,7 +108,7 @@ std::string basename(std::string filename, bool greedy) { if (greedy) filename = strip_leading_dirs(filename); - + std::string::size_type p = filename.find_last_of('/'); if (p == std::string::npos) p = filename.find_last_of('\\'); @@ -269,7 +269,7 @@ open_editor(const std::string& filename) { cmd = std::string(default_editor) + " \"" + filename + "\" &"; } - + int ret = system(cmd.c_str()); if (ret < 0) { From 6c69831e4c3b651d5b5921f5e95cb378958f8b21 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 6 Sep 2025 23:24:39 +0200 Subject: [PATCH 138/141] Add editor setting for rendering animations --- src/sprite/sprite.cpp | 7 +++++++ src/supertux/gameconfig.cpp | 3 +++ src/supertux/gameconfig.hpp | 1 + src/supertux/menu/editor_settings.cpp | 5 +++-- src/supertux/tile.cpp | 3 ++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/sprite/sprite.cpp b/src/sprite/sprite.cpp index ba64529224..d8d25041d8 100644 --- a/src/sprite/sprite.cpp +++ b/src/sprite/sprite.cpp @@ -18,6 +18,7 @@ #include +#include "editor/editor.hpp" #include "supertux/direction.hpp" #include "supertux/globals.hpp" #include "util/log.hpp" @@ -138,6 +139,12 @@ Sprite::animation_done() const void Sprite::update() { + if (Editor::is_active() && !g_config->editor_render_animations) + { + m_frameidx = 0; + return; + } + float frame_inc = m_last_ticks > 0.f ? m_action->fps * (g_game_time - m_last_ticks) : 0.f; m_last_ticks = g_game_time; diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index 313eed528f..b6450a6ddf 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -103,6 +103,7 @@ Config::Config() : editor_render_grid(true), editor_snap_to_grid(true), editor_render_background(true), + editor_render_animations(true), editor_render_lighting(false), editor_autotile_mode(false), editor_autotile_help(true), @@ -239,6 +240,7 @@ Config::load() editor_mapping->get("autotile_help", editor_autotile_help); editor_mapping->get("autotile_mode", editor_autotile_mode); editor_mapping->get("render_background", editor_render_background); + editor_mapping->get("render_animations", editor_render_animations); editor_mapping->get("render_grid", editor_render_grid); editor_mapping->get("render_lighting", editor_render_lighting); editor_mapping->get("selected_snap_grid_size", editor_selected_snap_grid_size); @@ -506,6 +508,7 @@ Config::save() writer.write("autotile_help", editor_autotile_help); writer.write("autotile_mode", editor_autotile_mode); writer.write("render_background", editor_render_background); + writer.write("render_animations", editor_render_animations); writer.write("render_grid", editor_render_grid); writer.write("render_lighting", editor_render_lighting); writer.write("selected_snap_grid_size", editor_selected_snap_grid_size); diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index 93c86901a4..5e4bb1507e 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -144,6 +144,7 @@ class Config final bool editor_render_grid; bool editor_snap_to_grid; bool editor_render_background; + bool editor_render_animations; bool editor_render_lighting; bool editor_autotile_mode; bool editor_autotile_help; diff --git a/src/supertux/menu/editor_settings.cpp b/src/supertux/menu/editor_settings.cpp index 9c7d7ce625..f0f5e90f97 100644 --- a/src/supertux/menu/editor_settings.cpp +++ b/src/supertux/menu/editor_settings.cpp @@ -31,12 +31,13 @@ EditorSettings::EditorSettings() snap_grid_sizes.push_back(_("small tile (8px)")); snap_grid_sizes.push_back(_("medium tile (16px)")); snap_grid_sizes.push_back(_("big tile (32px)")); - + add_string_select(-1, _("Grid Size"), &(g_config->editor_selected_snap_grid_size), snap_grid_sizes); add_toggle(-1, _("Show Grid"), &(g_config->editor_render_grid)); add_toggle(-1, _("Grid Snapping"), &(g_config->editor_snap_to_grid)); add_toggle(-1, _("Render Background"), &(g_config->editor_render_background)); add_toggle(-1, _("Render Light"), &(Compositor::s_render_lighting)); + add_toggle(-1, _("Render Animations"), &(g_config->editor_render_animations)); add_toggle(-1, _("Autotile Mode"), &(g_config->editor_autotile_mode)); add_toggle(-1, _("Enable Autotile Help"), &(g_config->editor_autotile_help)); add_toggle(-1, _("Enable Object Undo Tracking"), &(g_config->editor_undo_tracking)); @@ -49,7 +50,7 @@ EditorSettings::EditorSettings() add_intfield(_("Undo Stack Size"), &(g_config->editor_undo_stack_size), -1, true); } add_intfield(_("Autosave Frequency"), &(g_config->editor_autosave_frequency)); - + add_hl(); add_back(_("Back")); } diff --git a/src/supertux/tile.cpp b/src/supertux/tile.cpp index 189cb8bebc..22766a9441 100644 --- a/src/supertux/tile.cpp +++ b/src/supertux/tile.cpp @@ -20,6 +20,7 @@ #include "math/aatriangle.hpp" #include "supertux/constants.hpp" +#include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" #include "util/log.hpp" #include "video/drawing_context.hpp" @@ -221,7 +222,7 @@ Tile::get_current_surface() const SurfacePtr Tile::get_current_editor_surface() const { - if (m_editor_images.size() > 1) { + if (g_config->editor_render_animations && m_editor_images.size() > 1) { size_t frame = size_t(g_game_time * m_fps) % m_editor_images.size(); return m_editor_images[frame]; } else if (m_editor_images.size() == 1) { From 2bcfedbafb9617b62f32e9a57f505dce5cb8ab97 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sat, 6 Sep 2025 23:59:26 +0200 Subject: [PATCH 139/141] Initialize m_scrollbar in initialization list --- src/editor/tilebox.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/editor/tilebox.cpp b/src/editor/tilebox.cpp index f09a6008aa..8ec0bab1a3 100644 --- a/src/editor/tilebox.cpp +++ b/src/editor/tilebox.cpp @@ -48,7 +48,7 @@ EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : m_active_objectgroup(), m_object_info(new ObjectInfo()), m_on_select_callback([](EditorTilebox&) {}), - m_scrollbar(), + m_scrollbar(new ControlScrollbar(1.f, 1.f, m_scroll_progress, 35.f)), m_scroll_progress(1.f), m_hovered_item(HoveredItem::NONE), m_hovered_tile(-1), @@ -57,7 +57,6 @@ EditorTilebox::EditorTilebox(Editor& editor, const Rectf& rect) : m_mouse_pos(0, 0), m_shadow(SpriteManager::current()->create("images/engine/editor/shadow2.png")) { - m_scrollbar.reset(new ControlScrollbar(1.f, 1.f, m_scroll_progress, 35.f)); } void @@ -68,7 +67,7 @@ EditorTilebox::draw(DrawingContext& context) g_config->editorcolor, 0.0f, LAYER_GUI-10); context.color().set_blur(0); - + if (m_dragging) { context.color().draw_filled_rect(selection_draw_rect(), Color(0.2f, 0.4f, 1.0f, 0.6f), @@ -83,12 +82,12 @@ EditorTilebox::draw(DrawingContext& context) g_config->editorhovercolor, 0.0f, LAYER_GUI - 5); } - + // Shadow constexpr float SCROLL_SHADOW_MAX = 10.f; float scroll_shadow_size = std::clamp(m_scroll_progress * 0.1, 0.f, SCROLL_SHADOW_MAX); float scroll_shadow_normal = scroll_shadow_size / SCROLL_SHADOW_MAX; - + context.set_alpha(scroll_shadow_normal * 0.3); m_shadow->draw_scaled( context.color(), @@ -427,9 +426,9 @@ EditorTilebox::change_tilegroup(int dir) if (m_input_type == InputType::OBJECT) { select_last_tilegroup(); - return; + return; } - + m_tilegroup_id += dir; size_t tilegroups_size = m_editor.get_tileset()->get_tilegroups().size(); if (m_tilegroup_id < 0) @@ -448,7 +447,7 @@ EditorTilebox::change_objectgroup(int dir) select_last_objectgroup(); return; } - + m_objectgroup_id += dir; size_t objectgroups_size = m_object_info->m_groups.size(); if (m_objectgroup_id < 0) From 6453109511342a63cc9c2be544e5aea107453443 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 7 Sep 2025 10:18:03 +0200 Subject: [PATCH 140/141] Fix tile animations when animation display setting was disabled --- src/supertux/tile.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/supertux/tile.cpp b/src/supertux/tile.cpp index 22766a9441..5f1232dfda 100644 --- a/src/supertux/tile.cpp +++ b/src/supertux/tile.cpp @@ -18,6 +18,7 @@ #include "supertux/tile.hpp" +#include "editor/editor.hpp" #include "math/aatriangle.hpp" #include "supertux/constants.hpp" #include "supertux/gameconfig.hpp" @@ -209,10 +210,12 @@ Tile::draw_debug(Canvas& canvas, const Vector& pos, int z_pos, const Color& colo SurfacePtr Tile::get_current_surface() const { - if (m_images.size() > 1) { + // Check for editor's "Render animations" setting in case we call this method from the `get_current_editor_surface` method. + auto display_animations = (Editor::is_active() && g_config->editor_render_animations || !Editor::is_active()); + if (display_animations && m_images.size() > 1) { size_t frame = size_t(g_game_time * m_fps) % m_images.size(); return m_images[frame]; - } else if (m_images.size() == 1) { + } else if (Editor::is_active() || m_images.size() == 1) { return m_images[0]; } else { return {}; From 6e3ebbc10dc77f01b0aeb74c765a48dc7c547228 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Sun, 7 Sep 2025 10:42:27 +0200 Subject: [PATCH 141/141] Add more descriptions for editor settings --- src/badguy/boss.cpp | 12 ++++++++---- src/badguy/darttrap.cpp | 2 +- src/supertux/game_object.cpp | 8 +++++--- src/supertux/moving_object.cpp | 6 ++++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/badguy/boss.cpp b/src/badguy/boss.cpp index 78144cdd1e..639ca4f339 100644 --- a/src/badguy/boss.cpp +++ b/src/badguy/boss.cpp @@ -92,19 +92,23 @@ Boss::get_settings() { ObjectSettings result = BadGuy::get_settings(); - result.add_text("hud-icon", &m_hud_icon, "hud-icon", "images/creatures/yeti/hudlife.png", OPTION_HIDDEN); - result.add_int(_("Lives"), &m_lives, "lives", DEFAULT_LIVES); + result.add_text("hud-icon", &m_hud_icon, "hud-icon", "images/creatures/yeti/hudlife.png", OPTION_HIDDEN) + ->set_description(_("The icon that is displayed at the top indicating how many lives this boss has left")); + result.add_int(_("Lives"), &m_lives, "lives", DEFAULT_LIVES) + ->set_description(_("The maximum number of lives this boss has")); /* l10n: Pinch Mode refers to a particular boss mode that gets activated once the boss has lost the specified amounts of lives. This setting specifies how many lives need to be spent until pinch mode is activated. */ - result.add_int(_("Lives to Pinch Mode"), &m_pinch_lives, "pinch-lives", DEFAULT_PINCH_LIVES); + result.add_int(_("Lives to Pinch Mode"), &m_pinch_lives, "pinch-lives", DEFAULT_PINCH_LIVES) + ->set_description(_("Specifies how many lives need to be spent until pinch mode (a special boss mode) is activated")); /* l10n: Pinch Mode refers to a particular boss mode that gets activated once the boss has lost the specified amounts of lives. This setting specifies the squirrel script that gets run to activate boss mode. */ - result.add_script(get_uid(), _("Pinch Mode Activation Script"), &m_pinch_activation_script, "pinch-activation-script"); + result.add_script(get_uid(), _("Pinch Mode Activation Script"), &m_pinch_activation_script, "pinch-activation-script") + ->set_description(_("The script that gets run when pinch mode is activated")); return result; } diff --git a/src/badguy/darttrap.cpp b/src/badguy/darttrap.cpp index 7c08565632..69b3912b97 100644 --- a/src/badguy/darttrap.cpp +++ b/src/badguy/darttrap.cpp @@ -159,7 +159,7 @@ DartTrap::get_settings() result.add_float(_("Fire delay"), &m_fire_delay, "fire-delay") ->set_description(_("Time between consecutive darts.")); result.add_int(_("Ammo"), &m_ammo, "ammo") - ->set_description(_("Number of darts the trap can fire.")); + ->set_description(_("Number of darts the trap can fire. A value of -1 means infinite.")); result.add_sprite(_("Dart sprite"), &m_dart_sprite, "dart-sprite", "images/creatures/darttrap/granito/root_dart.sprite") ->set_description(_("Sprite used for the dart.")); diff --git a/src/supertux/game_object.cpp b/src/supertux/game_object.cpp index e960910b25..667a5013ec 100644 --- a/src/supertux/game_object.cpp +++ b/src/supertux/game_object.cpp @@ -68,7 +68,7 @@ GameObject::GameObject(GameObject* obj) : { m_fade_helpers.emplace_back(std::make_unique(fade_helper.get())); } - + // for (auto &component : obj->m_components) // { // m_components.emplace_back(std::make_unique(component.get())); @@ -133,7 +133,8 @@ GameObject::get_settings() ObjectSettings result(get_display_name(), get_uid()); result.add_int(_("Version"), &m_version, "version", 1, OPTION_HIDDEN); - result.add_text(_("Name"), &m_name, "name", ""); + result.add_text(_("Name"), &m_name, "name", "") + ->set_description(_("The name of this object. Must not contain any spaces if this object is to be referenced from scripts")); const GameObjectTypes types = get_types(); if (!types.empty()) @@ -147,7 +148,8 @@ GameObject::get_settings() names.push_back(type.name); } - result.add_enum(_("Type"), &m_type, names, ids, 0, "type"); + result.add_enum(_("Type"), &m_type, names, ids, 0, "type") + ->set_description(_("The type of an object defines its theme and behaviour. This is mostly used to distinguish between forest and ice world types of this object")); } return result; diff --git a/src/supertux/moving_object.cpp b/src/supertux/moving_object.cpp index c636c2b8e3..052944f9b5 100644 --- a/src/supertux/moving_object.cpp +++ b/src/supertux/moving_object.cpp @@ -67,8 +67,10 @@ MovingObject::get_settings() result.add_float(_("Width"), &m_col.m_bbox.get_width(), "width", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); result.add_float(_("Height"), &m_col.m_bbox.get_height(), "height", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); } - result.add_float(_("X"), &m_col.m_bbox.get_left(), "x", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); - result.add_float(_("Y"), &m_col.m_bbox.get_top(), "y", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES); + result.add_float(_("X"), &m_col.m_bbox.get_left(), "x", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES) + ->set_description(_("The horizontal location of the object in pixels from the left.")); + result.add_float(_("Y"), &m_col.m_bbox.get_top(), "y", {}, OPTION_HIDDEN | OPTION_VISIBLE_PROPERTIES) + ->set_description(_("The vertical location of the object in pixels from the top.")); return result; }