From cabce32bc10da95154e9b2487de48c6380356df1 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 16 Jun 2025 11:04:53 -0700 Subject: [PATCH 1/6] WIP: layout working, need to fix observers getting despawned --- asset_sources/folder.svg | 47 ++ asset_sources/gear.svg | 56 +++ .../ChakraPetch-Regular-PixieWrangler.ttf | Bin 88648 -> 89044 bytes panels_poc.rs | 110 +++++ src/main.rs | 1 + src/ui/level_select.rs | 410 ++++++++++++------ 6 files changed, 491 insertions(+), 133 deletions(-) create mode 100644 asset_sources/folder.svg create mode 100644 asset_sources/gear.svg create mode 100644 panels_poc.rs diff --git a/asset_sources/folder.svg b/asset_sources/folder.svg new file mode 100644 index 0000000..fef7ab5 --- /dev/null +++ b/asset_sources/folder.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/asset_sources/gear.svg b/asset_sources/gear.svg new file mode 100644 index 0000000..ac606ec --- /dev/null +++ b/asset_sources/gear.svg @@ -0,0 +1,56 @@ + + + + Sketch1_adj.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = manual + + + + + + diff --git a/assets/fonts/ChakraPetch-Regular-PixieWrangler.ttf b/assets/fonts/ChakraPetch-Regular-PixieWrangler.ttf index ae2b59d1091f5228aa39d8f793be10b9a9117bd9..7130e3ba6cf3b24cd05568bfdbca629d905f06b3 100644 GIT binary patch delta 13261 zcmb7rd0@>~7XLlxy=+1xgeS6FlBhNNDiR`#T7n`}Nzm3Pnxd_&FGTD$_^PO?YNylE zb~HaxX^nQ&WONzL7*&m>N-RZDMNKz(zt6q*n=3h~uXLR6m08b4v^is?r- z3(+uA2#>eNj2{>^?eNA2grM1=&!0K3pm5{+qgDvf;WZ&#Ha%Xjun=%>$g9BJ{qea) zk5wGmeN6~I@NYUZ`_TgHtU#X=yMVlo7B?FZmrE{FAg~vnd(ED=XmQh}u?cwoLeNmE{JVdte5JQBE#I^Zj z5-iLdRXdjoUCgH5Le!pd?M7zE8c=)J?XNE3tKm-R*Z^TR&BuK`9PBC`Av%b1v6sqe zBfUbqjN=_P;5y4Ilni6J*F5VsTeYpkw!oHe%eM8m`P!g)^PiiyHZN{|w)qcIf%`w- zza+$+^>@}@I3vV`!xs)**mvRe3)?TO!&AwH;--0$s%do7@TTrfmL}gOANhR#RduaA zKmGjV^AYFv3vup+b7ew&-ABTOIP)(IXQPY*sdUSq<;I4_F2;tAi>;5#@ufe|9IB$_ z__cyo(kfal326=eQ8Unbx=uF~>?VEBzT8Fr=B{kfc<#neHAYy4+^o!?NHop#tRep5Za#ecCwcJn*n zs^JfC{gD5rum|n9kNA-N*WdZD{ny9*34g}_luvwwKj#L9XM{!YQT{g{vlD&6U-AiV zXdBAya+xkfxIo`BSqVk`e$EMmwNa3LRn79Y@ht{3w} zDXkMb#eT6w%o0!XAsWj6KnL?h8CQy#VyUPS)#OWEl)I#Z;4JYze<41iBwhvUSMW-y zL>eyVVAwuLkT6*dK5>;8?)Lz;=Oy0y6?f2hIvC4qO|!J@C!I_W}2b4XOwXk~8+!%yn$zoOZ{Oat zd++UiIV>n_bXa-Vj<7S~?%^@vGs4%09}2%4(JLZ9VoOAQ#JxWK`b_Jyvd@PPm>)=c zVCe%5ea(F{`xf@y)AxAa>;1a-v&Qtx?N{1wYrp;dn)==CKcN5U{ww?M?SF4T#DKH` z3kJM4pk~0Sft?114a^)kYv9s>`v#sE)NWAbpwdA#gMNvOhiWbq{(E-sB(J|3!(IcbtqGv@HMwdpfjNTNzBf2K~ zpf&nPbYt|z=v&dh#kj}##)QQ5i%E#dh{=ta8Z$d)QA|b5+L$dduf^2F9EiCbb33Lv zwq0!3*dDP1Vv}MsW5>l#i=7kuOziU5&9Se>?vC9bdpP!3?Ah3BvG?L! z6qgw{E^b=foVaJ=md34%+Zy+J+@81(;~L`5#$AgGz8BB&Uh&=I!{VdjQ{zX(PmG@t zKR>=WetG=H`0eq#jVqRL>#GU76ZR(@PB@luHsM;ry+ls*O6;B(mKc?oioYWhrzFl! zd?s;O;+Di$6W>fcl6Wfda^i1E-IMwyr6jFS+Mcv0sXpmM(&eOENxvkUlf9C=CHF|~ zmmHToI(c^T^5o6Qdy~&5|B})@B_m~G%JP(&l*1`!2ZszEJ-Br6&cSC=y{xI(simoJ zr=Cb{9uhGmf5_S)htfDLB5hjQ{Iv44b!pqvcBg%qb|UR&x@&q+dPaI)`ZMXv(l@5R zmcBpz%1|+M(9qnWbA~P(x^d`^q5Fo`4?Q*X%Fy42wHp>RY{0OQ!yX&9df4t^^~0Jn zL`G^xX~ugQ&BI3wFCD&n__@pnGBY!0WM-|++?Uyqc{!_nR#euMtn#cKS#M|U&uYqU zpPi9in!P@|Ci`d(=LF;=?`w;NX*< zRGaCr05>?}u|`b+XAkjAJK-J9Tj&;eOyDucK5ku1@!~U3`6BV;nW{mBmCD!AeV-by4eP*K1X{P0dSAxapkkHPr#0f5~iB`Q?jX??TK;Lr-Qr~e7eIHVNCffD& zPL%p;Y)42=SNaZC*dik>v99A{>Z!&i1^S-n&yAoYixHUA&aap-4j2s$e--@>2bYOT zjI<;vvn}?z1G}WQFvnq)8Q=K$s?mLl?3qSDdDk81ceyx0l8%|UO~aNB?2;Fnp- zxdS;&i;Dq(N)vGVr477D!)-Sq-&M&6eE=AW?TE`jc(Lq23$hvkDJAfJsIe!dr}06G z^nRd2#h>|raWlnY+(?n$Zwr?a#w(MohWFr)9lpbu(RKK)d9cN}KUn%s8?51yYqFgy zB-KJiMndXLtCa#&k2=~%fJR4qsXa=y-zxyuelN9qT+^33=*yhw_d4jyTIg-*s8`%oYRqJ^l>x zvxP7tlxG_OX?508`()SADIKn(v(!F_ItfUJ>m*P{DJ|j<&C=nTzRZbUny2Z@o#adN zG<~_Iw~Aa0M4}oY$$$}}ua#rdZRzx*8X(QEiV76`_gSgZsRQW`O<&m>g`BJW(O3Dy zF+jF3`kAeg;X&1LF@-orG}k08)(mAW4p$7)VohJ>L@zDY^o{{l^wMHYU*1CBe2#OK z!Jr=ri&qf#R|uta$zbwwFgz|^c37-Ow4dFATr~s`OLvuJ;8*#1+t4?xCH4&b1F$BR znM=?55_>k#nOV*_otaDR^g4aW0MPWMc6uqtMY%)Mm)YrW0d7_Spy|sr{ZJ8ROBaWg zeUgD`)f}PB=mmH^??wlsG#n+iGrbTt)NuJ`(X1q`bh=#7=>M7)MR8~5qefR2$_=_Fr9rKYdc@{LzBJ42Iwna^X!>ZMZ7*q>Prm`=*2 z_GB$H*fcd@(Moce6TOUIO<(3jFAEM$zto9du0NW7sia4t@LAR+c(Nv2F7_w2EwDXJ z*&e5DH_idOqEL^ov@}gwDzUKC@XoRLRoZ!B1@}?;7Uhu|qcmWP#IH)S#>(WUvBgGV zj)jVASLS#U1mOXKG-*)tuiQx~fx`v}T4~gec+#(ux~Wir)7gdVAt+C&INKQcpn=u= z+=CV)e59ABoDaHcC^fuCT8v46$rPYrWk%Xai(lIm_mD|eP#U|$ConD2k+B}wawn|JQ5sur90XRzoGe8(uZ%^F zt*pH@YA{(D<0?#H{8teuP(8&D?`{+I*rZkobl*b8c*YB>|?_^=Z@`! z$MNIGQv0=Q*T4ojh4~6L>79*)F^09$Y2BA9>N=rPulxU1e^xqWIZ1y9?qI6aSxYVZ zs5=4Ilc3UR5=eSI2`crZ!LkK)+7_FY_A*l!J(6Lv@^{NJr0FjMuEK(^x1~1)w9o_Y zujiDMZw_|Qf9XVTo#0^j(utvygW)?ThHMA@cTV)J9h`8YZ8rYsLKB1P#P#^LM z^$87ki*~d04DSuJwP!fpEhwNv1qFxd3ksIuU-Ny_+WS|ZYW|HLE0Eyk(*>0U1(nVB z$+Unizb#T&l9UD1TN2EmBRgrQ{iCqEXP&(SSy#!9vE(8y9)v=7l|r(vl5iYH1DVODj7{P z?0gdcv}kJOhk}coI$WbG9q=t`Q_(Q3k^NbfMS1+b(lFAlAx?3%2iT@&z*sx{E$sme z-dI(t>7u3Fsc6{-#dOgkmly4^Jg1_ls--A`A=b84QB_M-1Pj_$QEMCwi=3*cH4geb zZd-X&$u|oyUhS~q*T?ClweWYq*O)r zqy+lbvPTB1JtxUnX_J$x#98H;Ab42O_YeuNOirs7hD9`|4Z|97)>vMVJV6$Qx*92> z8l}6O!aGzxeOO5-vKJ_rrl?+|t(xBIL@%R5(_3kVF=ABUQF6-d1x*x8 z(gzu-3SMO|R3l*09yO@bG+bbBg2C>9yg1F23i)|}%N#p($rFB6s1PJJ1zhJ9m#z@1 zjB`sYeicsjgAKeITVZ%FwfKG8ieQz7PjVTQPqMESm2LaFp!;`%T{H?SEq<4rdRXG< z;gam%m;{Ymd%bKNt+aTklM~&mdUEu-%mUwGk%jNl;k&kb@G2t>*b1i|xAdLfaaS0H zz?L{+<^EG+ON<@Be%p#=Pvu#Kg=dd0vlv-b+FA{}vaw1!Rv$*niNwmW8hO+k6o z(=?UGeot>d3)Z#$71O7V4%#S$4x}S=2hIxN2;QzxNvi>%m#)$DB`pRZ!C@<7r%pWTE=38 zwp3+W8H<{=!WaZ>i4#`NRE;gE&0k$$vTj0J)vr8)@XBKj$^mpyo zY~v6+{-^_=uJG&p6rPR(&%CNO|AiNP#>k4mm2cj;EQyz_JAT*GXZZb3I)>k0&?)>r z&HqOb@8LSEDeplylKKb}e}%X21g%a1ewwf2_YIRfJ`TYkVCy1VJM>a1P_olXNL!5_ zRbRaTs;7@}{RExqZkTVdO|S`aaT04Xxz`>$GMKHi@F_t)70?WXyRy5*YLNfLKl3mA zs|g3U_-N%nCbP-K)rROBD|9=u`_?PV2)cb9>1!EtIdB94Zd;3;F| z(Vl&lsjpC0%FzL>9H&aqlKOtd9rP#Z5T1zsZfrRkZ2e6_@NDM$j$h4=UvMa}W`P%| z*c}H*2h&$9@h24hmn``)qWF}@1gA@R> z34?x|zN8cM72Nqf{{XznWHzS#+hVm(DCvICnY7zsCgEEmQndxaRQaJ!3r-q_V3&dZ z|DcoZf%{abnN>sy6|!dj5C1CKBOuHeM3Zt~CxQIk4We}iLAOdVg zz%l>c2imCdzbU64^YfRpr&Y#Dd{HG0c#Hl*?-=WkSrQ>psbGiwmm%J2oV6h&T(r~p zp2=jKJLYF?fw#J(t(+d{OBQ9)E+1T7+eGyj@)`5wKP@xmzk2TE)HBmZd;<_K_$6P@ zIf3ntnL_mRaYh;IznDIJJEp}Zex5gTHE-b;cq_k%_2DH@a@!!sQ;=xS8+aq8PYI^Y zGrWe^q7Yigf8_N>%<;bC_d>bO2+=+2Zo!66K6k|zW%4cu&djxT9OHX4FMd}&1&E3^ z6#MKjNnK~`IsT;eB>K+fMqEcJ8Aw+2Z}sc6{g+9MP!Y*E!S%ssM6;X|jXk~G#B{#R zclk%~`pC5MyJVtMNK53){=#rral_i;dyIm8GGJs|Gu3}b7UD<7Al%28wJyjdEgiQF z_Wa9^tHY@_vWcxu~zs2>E?H=OijO|RxIpfXosg;KG(z7XJxey zoh&1d8MfEC9a?+i)Tv1x*|r**f;QDiJw4De$X3IZwrvpQYO5EXwt6l>D>o`mPw42N z1dXq)$w)ihzl)@wi!;{wXoYAC&?;-g&YY$vvTO~MZJR?m5Iz#WN7?FW4DMsm#-mL@ zn}n7J_++KT9C}zP0-i^3p9b7?;PP!vRDj2O)#kmCN;sNCYaO&lNw=41I%cE z8I8(}nSeiv=c{PnpfopTxZo_or}QfcrvYN7K8#bCd;*aK6;G z5!y;k8=++*v}}Zyjda5(zc7Z9cdrqRoAeF9pN>a!mG)uXR^^iz*M z5IZ6n*j{L1Xc1^}XfuFqht?jgjn z1C0G$$HumcpAN~kZNm^XBCNM5!=~acjhlhi#yBX_2#JlqmuS3wX|mfOjC`c6(Qv;U z*d-l9^gHvo(ulhp8!08)UG+bt$>~v}20;$M0%P0d$UZY{*Fk+9eP2i4jj*N>)-=Kz zIc##s1y??$Vc9g2rqClat!43|D|C&%ryp<@dYA6e&-5$Z zCmWU~W;2e=+}NExxE*_Pd+xwq?9H8wifev8gE*RFIUb)`B=cartV=ig{p#mZDY#>5 z?cr;^-K`_>1%exmrqRNaa&hG9iMMd$aD50HQ%{;m6LHO>JS3s1G!+SH8g|fbP%2+| zppKn^0_h4}0puE8!}U5{2PKYok<#wcU0mulz1bVrPTUDsU-lIp@Og4q;bm;U)7K@9)9^03;Z7F08C~!83HIc< zJQo!6c|NX%T!`xeUVySxmJG*h^X^)iMD2-t!>QKqr?+{qbna+aI}TP(gmqJ3n{@OH znNEbU?>kjqWYnMWmu7>&7a7u$4`?;bI?hsHr^;ZwqnK_gL%SX{DED>X947@Xz z#^O32XDuEyfhOQOi6-GXnI_}-9Y3w#>50`Tv_fDT=8Pt1y?tA6K)uP4_rOj6YlN69dPZ)9pOfL~Nnt3`;2WbHpY&KCB%R*j= zrxGr~DqhazsxEZpH|oMncq>ez?ll)0PIce9;^a9r2Ywg>`^UrXNwD`}*f|~c&4gVy zWDr-+_Ml*sg}YU+_Mq_o>72zm{2-6wF+7&X^8}v6llftO1UeM(Jbsd&;)UqDT(H}+ zx-%YRBa2)N35#7ZWd`(k}R)0k4P- z^e6fgAg|NwxV}MeVBmJqE?nQFw=lT7=^gy8rCQM*mDyg<)KQ)AqW1^U`#}E{wYwQR z;ZqpKGmKIId%|zv<8yotzniclHcQ*+2pz4P(}|KPeuwhzDjHtB{o2beKm*NTnBH!>5RZe`=4R}w$DvsAo$$$ zc28}X+bh+)m5feGMrS3XyOM$QSIgn}FAI1PyHanh;jK2$dUu8WG`7dp2glLtKmTW;(iC;}qjl6#3}ic9LaVmZowffzKhGG7%l#<9M# zrX0udra0u3IgDx3@L8b|sl#GON@k{|sl4CsoOAEx-g zeQAfb-D{0$m!7Au*B9#P`f$Cc?#Wo^>zyxmZtC3Fxk4y>@Y91<#(sF=hl#bF-Tk$KmHKzs1VytCK%lH<*mfkHsE#1ZM=6$j+X?~)4PV=CqV~jObHsv#R zaj<|hcHuur-+9~+6x#mEwD2Q6wO706n3mCU;8xJ@sEkB#%V{%hp=SjpRnYT_gI=Jk zbWOrK=v(f?dy0GBi}zM|-iJSp-y8Vv`9{7;;&ON{&jT!<7w|${i@1pwTc}HTDIm}A zGQJlDg4;i#?c=Xn(690T;ji;IBxb(__9lNz{{Az6#{xOP{{pUq{5|x?`9CD~eGBdb ze!_D1AxCwHKmIR%il63Z-hijvjPmFPx-Y&u;s1p6wXBeruqCV%Ra{BEsJ9H42oQqB{?0#TA5uKu4DbKI%Y_k1+{6$?UWj;= zv90VG_B^X#FS2dyB^2CFwu_%*J6IL_BYTDYKr89@>?vALPtnsXk2cZ)qg-kMq%nn%)k`uBuWLL<(kYgcDA)S36>KoTL zyYHdCE&T%ft?sv@Uu}Qa{%QTU_HP^zHelm`lc7UGSBIV$=s7TP;KqUb2c92zXHe9j ztU+4`)eLGM{LtVjg9`>98+>C(;E<_9%7z>r(loTs(D_4m4{dyS(8Kc|-uv+BVeZ3* z4@({P)Ud6?-Wq1AA9i!N&+z!+Q-+rh-#@%*gy)ErWxrWUQD-L@<>`r)8_{#8O;kDss!!Lxlhu?}|5uOo#5up)b z5o01UA~GXZL~MvCj@TNpJL2_-LlMU#PDj*7G(~ho+=+CJ^otCQ3^PTJiOh(c9hnum zIx;7+EOJ|9RpeWd??#@8{3Nm=@^a*j$oo;QQN5xPqb5Z?7PUBPZB$-VdDQl(y-{yR z9gR8}RTp(B>Uz}A(azC6(ZSIVN5@2`L{E)g5WO}!FSd-UGux1*0npNy`HZj5e= zz8T#a;~vu|W>8E_OiIkun0Ya=%VM62DT=9x*%k9z%)ywNm{T#I#axQH8uMeUb8Mg3 z{;?xt<73leXT~mwT^YMEwlwyI*gdiPV-IUhzJpD>;$DwC9CsqFF786yjkupj^Unc*Q$LoCjT*ab?7p$}iN?f{iH{`~Cmu_@Jno@! z^TrjAJ38*vxbvoQZR37Sa!%@#G%_hIX+hG)q^hJtNp(pVlCCD*PxeVpO3qB)lKfWk z$>a;kSCj9gc%=lUM5H98%uZRHvN5GR<@J>JQ(DG5j}IIlKR$E(uJQG$-l>nJzLr`) z!FfX5gwhFnC)7;1oaU7_C@nEb(Q=CKe@KXCugu zviNat7rs;59{CPwT4L05({^EQ-R0e%Z(hs1Im_>bc>%PsSb_XO3;1H@-Cw}Lw}Vcx zT{iqYfoB`tnfGA6i*7)Qfp6CQqLq?N0Q+oi@>A_OW&-rnf0h(oP3$FGD$KvCe}wm+av}hJqIS6~Z2tySlmuA^fuC~GR9N@wMMPH<)#{CuUFM#_^alO-L+T5Ql%^PiT z-ySZw6}*TP9P2MnmUfMm^f}t>(bc9+G(e_o418+l{{hzoU=5T*NCgt2;3Aa@p6dXA z%?8hNfLGbzMGo)|8@yP+nL(?Fe_-510Ze;3p}+Q*1b3%g{9xLYpg)Gv-#^fU({UmELuy-ZvEa}-M3epaa8IT}+ zQjlB|- zAk(bM_%Z%5STm%uvok4I%#u-Acgmf=&_d~F}F1s3cLV2v_<3R|F^1$N6yHJC#6 zWLC=$Lh>V0@|NwXzEr6Br7oG6OEn?^`WXbmTLglZs6b#xCyIJ{XA^%^1_(zZOcUjl zDB+Vko9K?r^)v<7YE#q4=V0?GW_65ST44Aq6k^VXqdDyNti>V?+hM@NEYgNcX-SZ_ zKuE(1F?B+a|8jao!gGWisYS>!&~mM8f*HlFCSDQFo3glPN^FicJx!znw9q9iyeZ2< zVav5*U`ri%g(V7GsvS!6hy0Jzc2d5Ul`e*)nk3ZpUIfZW=}UpOE!|h!lVLpy+eWRTDi35wsWAg~q^|*rx zvlK(Ig94FmrJ%%tUaX;tzQjt8IeE5Bv7k5Lz=|S+V`+f*Ad{G^6~h)fA`wHdGRDdr zCrFQT>5z;cmTDY4MT{Spf^uoGq*qJ3NSRvNb7_dAPs1p_F6p1J(Bpvv6f1hMMF71v zztqEoV&KlQNHCsdk@S-iU(;xioObPMPKMZFDz8_ zr4IDR-E9n|X5-Ycq#c>I*Hr4T2nw&&B3P=n6u;V^l{!p~f?iF2rItN}8o9y-HA+h@ z+byvYsY!p9^s!WpbZcD*29@Vhhdc`fDs83KJj+6Dv(odc_Vk88D?Q+Lg>4M6(SPnh zzv@vN!{-hRJ~oDL92hce^xrtpcPXfMpzo4jFBU<>+p~yvWl4;K3pBz1THXb%vF)#lrAfMD>mBCyWvfI(`WDmQn`T;$Zd5Kp~=1%bX=&tyCF8aP*^EzW!f>n?lX zEHNAO4Q#7^2$+1p!px0xCxlX&*0;#lzl{1z>C^QMe5O>3(E>9owCTWBSg@i>r_0%H z7_bnd6$6`Z$-#6z)543D61-*FAt^c3B00tZ`>fUk?6cyLCE_o5?N{3}&D-QVx9s;! z8h%Y+rQR?|9t`ubb_)$8*JS-0JrisAV#EaVvBYBy$GT+Uhx82&(krxL6TDK=rS88= zpTn(o16xM#D9hmU40)i!yaJyKwI*Q2oMN?mhU67k;j?$K;FYP6dhM|lS`x4oHf$!h zUtP|*$wEq#_&B}t#fS_+@VS}J|ckebKA=QVPZCDzmdh{>0@!e<-*1_$XCT9eRi z!K#IH86u_@(n8I<1YTK}S;X7ESj)5|V9Ttf32aws3ZJtDmR*u|1tZq>*a~eQuoV_8 zstT)zSe_DKtqacEXG?qyn`p6CQ``zWlS+O4iyYPvfyJ62bC3mWA*mH30`@q| z9EkZ_*s~4TGRy4V4KwHhLn3&^coBo*MCmG18RiF(zcGknvmAbE{w}i|3FUlZI0G&* zpSY08VZPS^SM(-(dO2N6dj546JvSP}Y$oXehh$whsfP)63~gNu)NIGl=AdAN)gXSw zj{cruiA}*32YRt~DTCVW=$j3uBpXA!l>t%Alp+tVg?ms%q~(g%EFTYz>67F z;H5{ua;VusVOIp21-%KO$AxFwMWE$37+M?@OtyK{>Ofy&bEwsUzAFNk?dVO-hOP)) zwlc^Fh`pJLK!ITRRRqKyO+}zU@YzK`%wfG`goaw+SUuoqGHT5J&{swQcvcStCM!H7 z5=AB$M3LDaKShzLbD+s?$?Rni&tw#Ro&&wuoGN+9>1|vbSdJ`iyaoFN;*6Xad}xUOu%|@*U`{ zPY*FanZO`IsuT!|ECzu=gj6YTG)RP0(c7J#K`%n8=xq}eBGfJ;D(FQ-Z4u(dGNLBM zU=5Kv9E$D3Sn2U5hr=Xlf;}MxSv}|u1MZ#XT!36Jm3EN|XiPt7eB@52?zYE1x=EE;8 zad+EE@2l4+TJWBr0XuoPdL6>f$w*=GHw=f2!F&(B#}IkEC|R&4XyP6|1i$dwgf-ac zUIJZzUg3mc&iC-X7W`QoUS@)C^_O@$3q0qW&9!@8?lJ8Pq`{eY{Lm(dJFoL{mTt^N zy(swg?h5{Qz?U1Jw83u#Nx1I9%k{g)EF1i$`M`mpG>wbg@(w*#Z-dY{5WOUX8WjAx zK3Bp$0k084E$|z9wuDaxe5?>^f!{QT9_;5rF6Oi&^*j)6H`6OrMSrBd^e6TeyTC58 zChWogOZ&8#W5b=|c0ueUfcv@32#RFW%X| z&pu$s#d~{gUQI?O+lm+LABk7&-12(e@@9QE+rwUF-{IZ*XY38O9q+5(#5?RAY^QwJ zeonmT<_-Ky-pIe=7w{VVBIc|A@+NkIeaM@6i}rKPJaguI|1g+?K5*ma;UCWO)Jpy{ z$h46WX4eX4pyyBvL?7UcYoHVKA?{D&8xjMZLAHoKr3T!8$^XiTAL2(C=SN`(3FXYd z&*2q1!Gs3DzvNeOf6d^GZ##@wK@o8@cW5bPSX5~+t;j_wXbIoI5OW3n7jy!5L?3G2pM{uy z77+Y4@_V+s`?fm-4OknbyZ3AtnZEj*`S*f>e7OZWgtPzxSKYsCpyQO}~5K^OT;LJ=`U6ioQMNEQ;4m1^X-`CaCTI^bH?=l-S| zi5IoaB$X;OC*=JBa-)px1)7IgsLcezD~B#4h5X;CS-^o;co{FYF38Eum!^@&Va zW84Gx&UQoLQ}G)E>9%z!SuISpvG_Qs;Pe5%28b8pVi}bXVWhs)k3wmnBSv#Qx6ro= zV`@9s_zu1ksO(Id096{}YkARl^Vun%kN(aXH4StjV0Nx&uHPybLF|(-o#5x>=zCk!gTr*i? zis)KK2(oCKqCob`(n77sTJQwU8~0s`yRytNW<2bsK0DdqvM;|7QSIkZe!n41JNm`< zflF%^kXsLOY!>(+BaxdA<_*dd7yQJ>h5Sg(0b}MyboahxDP-JLJL9bhc*Ca{n#=Z zrZz}ZBUO zDqL6Nx(3&^xUR!B8`t%k-{m7iGo-H#$dmA^0e%Ue8sJX@{Aqwc4ah?S_t$P-o<%*Q=1529$J_wDWOXg{_Ow>}-X^21so9l|(JI4cfbIr( z(*SQ8;El+F$bg7GBCoA&pGLXn_uGdNE?2)fL+R->ndZ`bdcwN$P#d+=xAYy|z^n9I z^b_5sd-Q-h@!+3xBVO$~a~JN)-MBmV;GW!zd*f4EKdtD#Z;x;u$)kBJAI%f^SU!%Y zYJEF=ok|$*9&b+U80c!6jIX;~=n;B^dC)YPhJFUkKtBtQBs}O*dKCQ}nuAH`ae5pR z(i8Lq=oivL=7z26BJ7Xas11;IYDa&Su7dIg-N2NFxBlpF(Jl0y)X7|7F24DO$(V>? zwvoAU=P2%siz|0!?y%k+y(jl%!Ur$(-rO6#5BFi+@c6M8R>UwK#<=Evdze!y$EVm@ z((QEe(6-zj>fep8=Bq)smaj#h&9kxd5sUt3=Cij=+&tq>Dw!+qzRkyp@IC@xXTZlt z;oDqzCPKYPhWV-`$kyOG!#H7-2&xZ^5MdomB8>jp2lsnov)Ikg+;HE`8RzK?_&x=n zpcrW?O+|dB<0}U@nu#Y1ZZsR`e>ZxJ9z&l=nds-yJkZal`Izh%;3G35Wleq~9;?)CO zD3z*z=SuUrmVEyqw^)YLG(I^%!MHCQd~XX zgLLv0s~};O%R%wybDUg|<$>H>{Zuzf=G8~K3l;ag=??XAQR};C7jjTZmB_(v+6|hQ>1ELD!M8BZI1lf|GW{or=PZzB%0 z4aid?#|{9ey^9FiC4LF_O*q)1vOxDS)m2}I)fBr|oT0hpt9QPi{~35^oToNpvm(x$ zJ>-tOC-(oRSuc4?2oR^#>a5;W$X$j<)G=e^)auK=^ic$LuRJh`Cg8~Vji@ui80LM* zgZuII_z;Z4LvdtgQeZDBu(uQ#C!pI74UHNF0fq*eLLc2bnnD$wRQG zG|F4b@R2flN*NDH8QC+)%m?HD65!qMW_4Ufoa}z9%_a`J*qT;OnoSSFrD%B`7u!bR zxlkp}ES~br(@l!+ArII7;#ghXI2Yrja{U}i87#N1N*c=LH#Jo^&Y^$M_+KA{#EHF| N5X$=g|CQgN{{xRy^-KT& diff --git a/panels_poc.rs b/panels_poc.rs new file mode 100644 index 0000000..364232e --- /dev/null +++ b/panels_poc.rs @@ -0,0 +1,110 @@ +use bevy::{prelude::*, winit::WinitSettings}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) + .add_systems(Startup, setup) + .add_observer(add_items) + .run(); +} + +#[derive(Component)] +struct ItemPanel; +#[derive(Component)] +struct PanelContent; + +fn setup(mut commands: Commands) { + // ui camera + commands.spawn(Camera2d); + + commands.spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + padding: UiRect::all(Val::Px(20.0)), + ..default() + }, + children![( + Node { + display: Display::Grid, + grid_template_columns: vec![GridTrack::flex(0.75), GridTrack::flex(0.25)], + column_gap: Val::Px(20.), + width: Val::Percent(100.0), + height: Val::Percent(100.0), + ..default() + }, + children![(ItemPanel, panel()), panel(),] + )], + )); +} + +fn panel() -> impl Bundle { + ( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + flex_direction: FlexDirection::Column, + ..default() + }, + children![ + ( + Node { + width: Val::Px(100.), + padding: UiRect::all(Val::Px(5.0)), + ..default() + }, + BackgroundColor(Color::srgb(1.0, 0.0, 0.0).into()), + children![(Text::new("Panel"),)] + ), + ( + PanelContent, + Node { + width: Val::Percent(100.), + height: Val::Percent(100.), + padding: UiRect::all(Val::Px(5.0)), + flex_wrap: FlexWrap::Wrap, + overflow: Overflow::scroll_y(), + row_gap: Val::Px(5.0), + column_gap: Val::Px(5.0), + ..default() + }, + BackgroundColor(Color::srgb(0.0, 1.0, 0.0).into()), + children![(Text::new("Panel Content"),)] + ) + ], + ) +} + +fn item(index: usize) -> impl Bundle { + ( + Node { + width: Val::Px(50.0), + height: Val::Px(50.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + ..default() + }, + BackgroundColor(Color::srgb(0.0, 0.0, 1.0).into()), + children![Text::new(format!("{}", index)),], + ) +} + +fn add_items( + trigger: Trigger, + panel_content: Query<(), With>, + children: Query<&ChildOf>, + mut commands: Commands, +) { + for entity in children.iter_ancestors(trigger.target()) { + if panel_content.get(entity).is_ok() { + commands + .entity(trigger.target()) + .despawn_related::(); + for i in 0..50 { + commands.entity(trigger.target()).with_child(item(i + 1)); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index bba69cd..d7d4ba4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1180,6 +1180,7 @@ fn playing_exit_system( mut commands: Commands, query: Query, Without)>, ) { + // TODO RIP, we are despawning important observers for entity in query.iter() { commands.entity(entity).despawn(); } diff --git a/src/ui/level_select.rs b/src/ui/level_select.rs index f11606a..5b59250 100644 --- a/src/ui/level_select.rs +++ b/src/ui/level_select.rs @@ -1,4 +1,5 @@ use crate::{level::Level, loading::NUM_LEVELS, save::BestScores, theme, GameState, Handles}; + use bevy::prelude::*; pub struct LevelSelectPlugin; @@ -6,6 +7,10 @@ pub struct LevelSelectPlugin; pub struct LevelSelectScreen; #[derive(Component)] pub struct LevelSelectButton(u32); +#[derive(Component)] +struct SettingsPanelBody; +#[derive(Component)] +struct LevelsPanelBody; impl Plugin for LevelSelectPlugin { fn build(&self, app: &mut App) { @@ -18,9 +23,15 @@ impl Plugin for LevelSelectPlugin { ); app.add_systems(OnExit(GameState::LevelSelect), level_select_exit); + + // TODO these are not firing when re-entering GameState::LevelSelect?? + app.add_observer(populate_settings_panel_body); + app.add_observer(populate_levels_panel_body); } } +// TODO add "diagonal scrolling grid" background + fn level_select_button_system( query: Query<(&Interaction, &LevelSelectButton), Changed>, mut next_state: ResMut>, @@ -43,46 +54,64 @@ fn level_select_button_system( } } -fn level_select_enter( - mut commands: Commands, - best_scores: Res, - handles: Res, - levels: Res>, -) { +fn level_select_enter(mut commands: Commands, best_scores: Res, handles: Res) { let total_score: u32 = best_scores.0.iter().map(|(_, v)| v).sum(); - commands + let root = commands .spawn(( Node { - width: Val::Percent(100.), - height: Val::Percent(100.), + width: Val::Percent(100.0), + height: Val::Percent(100.0), flex_direction: FlexDirection::Column, - align_items: AlignItems::Center, - justify_content: JustifyContent::SpaceEvenly, + overflow: Overflow::clip(), ..default() }, LevelSelectScreen, )) + .id(); + + // TODO pretty sure this should be a bottom bar, and smaller + let top_bar = commands + .spawn(( + Node { + width: Val::Percent(100.), + flex_shrink: 0.0, + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + justify_content: JustifyContent::SpaceBetween, + padding: UiRect { + left: Val::Px(20.), + right: Val::Px(20.), + top: Val::Px(10.), + bottom: Val::Px(10.), + }, + ..default() + }, + BackgroundColor(theme::UI_PANEL_BACKGROUND.into()), + )) .with_children(|parent| { + parent.spawn(( + Node { + align_self: AlignSelf::Center, + ..default() + }, + Text::new("₽IXIE WRANGLER"), + TextFont { + font: handles.fonts[0].clone(), + font_size: 40.0, + ..default() + }, + TextColor(theme::PIXIE[1].into()), + )); + // Right side of top bar parent .spawn(Node { - flex_direction: FlexDirection::Column, + align_items: AlignItems::FlexStart, + justify_content: JustifyContent::Center, + column_gap: Val::Px(10.), ..default() }) .with_children(|parent| { - parent.spawn(( - Node { - align_self: AlignSelf::Center, - ..default() - }, - Text::new("₽IXIE WRANGLER"), - TextFont { - font: handles.fonts[0].clone(), - font_size: 50.0, - ..default() - }, - TextColor(theme::PIXIE[1].into()), - )); parent.spawn(( Node { align_self: AlignSelf::Center, @@ -96,118 +125,186 @@ fn level_select_enter( }, TextColor(theme::FINISHED_ROAD[1].into()), )); + // TODO clock for flavor }); + }) + .id(); - let cols = (NUM_LEVELS as f32 / 3.).ceil() as u16; + let main_content = commands + .spawn((Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + padding: UiRect::all(Val::Px(20.)), + column_gap: Val::Px(20.), + display: Display::Grid, + grid_template_columns: vec![GridTrack::flex(0.75), GridTrack::flex(0.25)], + ..default() + },)) + .id(); - parent - .spawn(Node { - display: Display::Grid, - grid_template_rows: RepeatedGridTrack::auto(3), - grid_template_columns: RepeatedGridTrack::auto(cols), - row_gap: Val::Px(10.), - column_gap: Val::Px(10.), + let settings_panel = commands + .spawn(panel( + "\u{01a9} SETTINGS", + &handles, + Node::default(), + SettingsPanelBody, + )) + .id(); + + let levels_panel = commands + .spawn(panel( + "\u{0393} /user/levels", + &handles, + Node { + column_gap: Val::Px(10.), + row_gap: Val::Px(10.), + flex_wrap: FlexWrap::Wrap, + align_content: AlignContent::FlexStart, + ..default() + }, + LevelsPanelBody, + )) + .id(); + + commands + .entity(main_content) + .add_child(levels_panel) + .add_child(settings_panel); + + commands.entity(root).add_child(top_bar); + commands.entity(root).add_child(main_content); +} + +fn panel( + title: impl Into, + handles: &Handles, + body_node: Node, + body_marker: M, +) -> impl Bundle { + ( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + flex_direction: FlexDirection::Column, + overflow: Overflow::hidden(), + ..default() + }, + Name::new("Panel"), + Children::spawn(( + Spawn(( + Name::new("PanelTitle"), + Node { + padding: UiRect { + left: Val::Px(20.), + right: Val::Px(20.), + top: Val::Px(10.), + bottom: Val::Px(10.), + }, + width: Val::Auto, ..default() - }) - .with_children(|parent| { - for i in 1..=NUM_LEVELS { - parent - .spawn(( - Button, - Node { - width: Val::Px(150.), - height: Val::Px(150.), - flex_direction: FlexDirection::Column, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - ..default() - }, - BackgroundColor(theme::UI_NORMAL_BUTTON.into()), - LevelSelectButton(i), - )) - .with_children(|parent| { - let level = handles - .levels - .get(i as usize - 1) - .and_then(|h| levels.get(h)); - - let level_color = match level { - Some(_) => theme::UI_LABEL, - None => theme::UI_LABEL_BAD, - }; - - let (score_text, star_text_one, star_text_two) = - if let (Some(score), Some(level)) = - (best_scores.0.get(&i), level) - { - let stars = level - .star_thresholds - .iter() - .filter(|t| **t <= *score) - .count(); - - ( - format!("Æ{score}"), - "★".repeat(stars), - "★".repeat(3 - stars), - ) - } else { - ("".to_string(), "".to_string(), "".to_string()) - }; - - parent - .spawn(( - Text::default(), - // See Bevy#16521 - TextFont { - font: handles.fonts[0].clone(), - ..default() - }, - )) - .with_children(|parent| { - parent.spawn(( - TextSpan::new(star_text_one), - TextFont { - font: handles.fonts[0].clone(), - font_size: 25.0, - ..default() - }, - TextColor(theme::UI_LABEL.into()), - )); - parent.spawn(( - TextSpan::new(star_text_two), - TextFont { - font: handles.fonts[0].clone(), - font_size: 25.0, - ..default() - }, - TextColor(theme::UI_LABEL_MUTED.into()), - )); - }); - - parent.spawn(( - Text::new(format!("{i}")), - TextFont { - font: handles.fonts[0].clone(), - font_size: 50.0, - ..default() - }, - TextColor(level_color.into()), - )); - - parent.spawn(( - Text::new(score_text), - TextFont { - font: handles.fonts[0].clone(), - font_size: 25.0, - ..default() - }, - TextColor(theme::FINISHED_ROAD[1].into()), - )); - }); - } - }); - }); + }, + BackgroundColor(theme::UI_NORMAL_BUTTON.into()), + Children::spawn(Spawn(( + Text::new(title), + TextFont { + font: handles.fonts[0].clone(), + font_size: 25.0, + ..default() + }, + TextColor(theme::UI_LABEL.into()), + ))), + )), + Spawn(( + Name::new("PanelBody"), + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + padding: UiRect::all(Val::Px(10.)), + overflow: Overflow::scroll_y(), + ..body_node + }, + BackgroundColor(theme::UI_PANEL_BACKGROUND.into()), + body_marker, + )), + )), + ) +} + +fn level_item( + level: &Level, + level_index: u32, + best_scores: &BestScores, + font_handle: &Handle, +) -> impl Bundle { + let (score_text, star_text_one, star_text_two) = + if let Some(score) = best_scores.0.get(&level_index) { + let stars = level + .star_thresholds + .iter() + .filter(|t| **t <= *score) + .count(); + + ( + format!("Æ{score}"), + "★".repeat(stars), + "★".repeat(3 - stars), + ) + } else { + ("".to_string(), "".to_string(), "".to_string()) + }; + + ( + Button, + Name::new("LevelItem"), + Node { + width: Val::Px(150.), + height: Val::Px(150.), + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + BackgroundColor(theme::UI_NORMAL_BUTTON.into()), + LevelSelectButton(level_index), + Children::spawn(( + Spawn(( + Text::new(star_text_one), + TextFont { + font: font_handle.clone(), + font_size: 25.0, + ..default() + }, + TextColor(theme::UI_LABEL.into()), + Children::spawn(Spawn(( + TextSpan::new(star_text_two), + TextFont { + font: font_handle.clone(), + font_size: 25.0, + ..default() + }, + TextColor(theme::UI_LABEL_MUTED.into()), + ))), + )), + Spawn(( + Text::new(format!("{level_index}")), + TextFont { + font: font_handle.clone(), + font_size: 50.0, + ..default() + }, + TextColor(theme::UI_LABEL.into()), + )), + Spawn(( + Text::new(score_text), + TextFont { + font: font_handle.clone(), + font_size: 25.0, + ..default() + }, + TextColor(theme::FINISHED_ROAD[1].into()), + )), + )), + ) } fn level_select_update() {} @@ -218,9 +315,56 @@ fn level_select_exit( mut mouse: ResMut>, ) { for entity in query.iter() { + info!("level_select_exit despawning: {entity}"); commands.entity(entity).despawn(); } mouse.reset(MouseButton::Left); mouse.clear(); } + +fn populate_settings_panel_body( + trigger: Trigger, + mut commands: Commands, + handles: Res, +) { + commands.entity(trigger.target()).with_child(( + Text::new("TODO"), + TextFont::from_font(handles.fonts[0].clone()), + )); + commands.entity(trigger.target()).with_child(( + Text::new("Music: 30"), + TextFont::from_font(handles.fonts[0].clone()), + )); + commands.entity(trigger.target()).with_child(( + Text::new("SFX: 30"), + TextFont::from_font(handles.fonts[0].clone()), + )); +} + +fn populate_levels_panel_body( + trigger: Trigger, + mut commands: Commands, + handles: Res, + best_scores: Res, + levels: Res>, +) { + info!("populate_levels_panel_body: {}", trigger.target()); + for level_index in 1..=NUM_LEVELS { + let Some(handle) = handles.levels.get(level_index as usize - 1) else { + warn!("No level handle for level {level_index}"); + continue; + }; + let Some(level) = levels.get(handle) else { + warn!("No level asset for level {level_index}"); + continue; + }; + + commands.entity(trigger.target()).with_child(level_item( + level, + level_index, + &best_scores, + &handles.fonts[0], + )); + } +} From 6fc34a7e8eae004bcaeb496e07f1dc89f964aa64 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 16 Jun 2025 11:28:18 -0700 Subject: [PATCH 2/6] WIP: fixed that --- panels_poc.rs | 3 --- src/main.rs | 55 +++++++++++++++++++++++------------------- src/net_ripping.rs | 5 ++-- src/pixie.rs | 2 ++ src/ui/score_dialog.rs | 1 + 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/panels_poc.rs b/panels_poc.rs index 364232e..f774247 100644 --- a/panels_poc.rs +++ b/panels_poc.rs @@ -16,9 +16,6 @@ struct ItemPanel; struct PanelContent; fn setup(mut commands: Commands) { - // ui camera - commands.spawn(Camera2d); - commands.spawn(( Node { width: Val::Percent(100.0), diff --git a/src/main.rs b/src/main.rs index d7d4ba4..88eb705 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,12 +96,12 @@ fn main() { )); app.init_state::(); + app.enable_state_scoped_entities::(); app.add_systems( OnEnter(GameState::Playing), (reset_game, spawn_level, spawn_game_ui).chain(), ); - app.add_systems(OnExit(GameState::Playing), playing_exit_system); app.configure_sets(Update, DrawingInput.run_if(in_state(GameState::Playing))); app.add_systems( @@ -562,12 +562,15 @@ fn pixie_button_system( let mut timer = Timer::from_seconds(duration * *count as f32, TimerMode::Repeating); timer.set_elapsed(Duration::from_secs_f32((*i + 1) as f32 * duration)); - commands.spawn(PixieEmitter { - flavor: *flavor, - path: world_path.clone(), - remaining: pixies, - timer, - }); + commands.spawn(( + PixieEmitter { + flavor: *flavor, + path: world_path.clone(), + remaining: pixies, + timer, + }, + StateScoped(GameState::Playing), + )); *i += 1; } @@ -679,9 +682,11 @@ fn draw_mouse_system( ShapeBuilder::with(&shape).stroke((color, 2.0)).build(), Transform::from_translation(mouse_snapped.0.extend(layer::CURSOR)), Cursor, + StateScoped(GameState::Playing), )); } + // TODO move this bit to a separate system in road_drawing.rs if !line_drawing.is_changed() { return; } @@ -704,6 +709,7 @@ fn draw_mouse_system( .build(), Transform::from_xyz(0.0, 0.0, layer::ROAD_OVERLAY), DrawingLine, + StateScoped(GameState::Playing), )); } } @@ -851,6 +857,7 @@ fn spawn_road_segment( .build(), Transform::from_xyz(0.0, 0.0, layer::ROAD - segment.layer as f32), segment.clone(), + StateScoped(GameState::Playing), )) .with_children(|parent| { parent.spawn(( @@ -890,6 +897,7 @@ fn spawn_obstacle(commands: &mut Commands, obstacle: &Obstacle) { .fill(theme::OBSTACLE) .build(), Transform::from_translation(origin.extend(layer::OBSTACLE)), + StateScoped(GameState::Playing), )) .with_children(|parent| { parent.spawn(( @@ -942,6 +950,7 @@ fn spawn_name( TextColor(theme::LEVEL_NAME.into()), Anchor::TopLeft, Transform::from_translation((name_position + Vec2::new(8., -8.)).extend(layer::GRID)), + StateScoped(GameState::Playing), )); } @@ -965,6 +974,7 @@ fn spawn_terminus( .build(), Transform::from_translation(terminus.point.extend(layer::TERMINUS)), terminus.clone(), + StateScoped(GameState::Playing), )) .with_children(|parent| { parent.spawn((Collider::Point(terminus.point), ColliderLayer(1))); @@ -1176,16 +1186,6 @@ fn update_elapsed_text_system( } } -fn playing_exit_system( - mut commands: Commands, - query: Query, Without)>, -) { - // TODO RIP, we are despawning important observers - for entity in query.iter() { - commands.entity(entity).despawn(); - } -} - fn save_solution_system( query: Query<&RoadSegment>, graph: Res, @@ -1237,6 +1237,7 @@ fn spawn_level( .build(), Transform::from_xyz(x as f32, y as f32, layer::GRID), GridPoint, + StateScoped(GameState::Playing), )); } } @@ -1309,14 +1310,18 @@ fn spawn_game_ui( let mut tool_button_ids = vec![]; commands - .spawn(Node { - width: Val::Percent(100.), - height: Val::Percent(100.), - flex_direction: FlexDirection::ColumnReverse, - justify_content: JustifyContent::FlexStart, - align_items: AlignItems::Center, - ..default() - }) + .spawn(( + Name::new("GameUiRoot"), + Node { + width: Val::Percent(100.), + height: Val::Percent(100.), + flex_direction: FlexDirection::ColumnReverse, + justify_content: JustifyContent::FlexStart, + align_items: AlignItems::Center, + ..default() + }, + StateScoped(GameState::Playing), + )) .with_children(|parent| { // bottom bar parent diff --git a/src/net_ripping.rs b/src/net_ripping.rs index 678f5b1..a6765c1 100644 --- a/src/net_ripping.rs +++ b/src/net_ripping.rs @@ -11,8 +11,8 @@ use crate::{ collision::{point_segment_collision, PointCollision}, layer, sim::SimulationState, - Collider, ColliderLayer, DrawingInteraction, DrawingMouseMovement, MouseSnappedPos, RoadGraph, - RoadSegment, SegmentGraphNodes, SelectedTool, Tool, + Collider, ColliderLayer, DrawingInteraction, DrawingMouseMovement, GameState, MouseSnappedPos, + RoadGraph, RoadSegment, SegmentGraphNodes, SelectedTool, Tool, }; pub struct NetRippingPlugin; @@ -159,6 +159,7 @@ fn draw_net_ripping_system( .build(), Transform::from_xyz(0.0, 0.0, layer::ROAD_OVERLAY), RippingLine, + StateScoped(GameState::Playing), )); } } diff --git a/src/pixie.rs b/src/pixie.rs index 45c78de..74ee698 100644 --- a/src/pixie.rs +++ b/src/pixie.rs @@ -170,6 +170,7 @@ pub fn explode_pixies_system(mut commands: Commands, query: Query<(Entity, &Pixi direction: Vec2::new(cos, sin), ..default() }, + StateScoped(GameState::Playing), )); } } @@ -518,6 +519,7 @@ pub fn emit_pixies_system(mut q_emitters: Query<&mut PixieEmitter>, mut commands path_index: 0, ..default() }, + StateScoped(GameState::Playing), )); emitter.remaining -= 1; diff --git a/src/ui/score_dialog.rs b/src/ui/score_dialog.rs index c3aeae6..9b057eb 100644 --- a/src/ui/score_dialog.rs +++ b/src/ui/score_dialog.rs @@ -95,6 +95,7 @@ fn show_score_dialog_system( ), BackgroundColor(theme::UI_PANEL_BACKGROUND.into()), ScoreDialog, + StateScoped(GameState::Playing), )) .with_children(|parent| { parent.spawn(Text::default()).with_children(|parent| { From 4f14064fd39822e9987e9508c1d405f273b09dc5 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 16 Jun 2025 11:36:55 -0700 Subject: [PATCH 3/6] WIP: small tweaks and tidying --- src/ui/level_select.rs | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/ui/level_select.rs b/src/ui/level_select.rs index 5b59250..0a0d8ec 100644 --- a/src/ui/level_select.rs +++ b/src/ui/level_select.rs @@ -18,8 +18,7 @@ impl Plugin for LevelSelectPlugin { app.add_systems( Update, - (level_select_update, level_select_button_system) - .run_if(in_state(GameState::LevelSelect)), + (level_select_button_system).run_if(in_state(GameState::LevelSelect)), ); app.add_systems(OnExit(GameState::LevelSelect), level_select_exit); @@ -70,8 +69,7 @@ fn level_select_enter(mut commands: Commands, best_scores: Res, hand )) .id(); - // TODO pretty sure this should be a bottom bar, and smaller - let top_bar = commands + let bottom_bar = commands .spawn(( Node { width: Val::Percent(100.), @@ -98,7 +96,7 @@ fn level_select_enter(mut commands: Commands, best_scores: Res, hand Text::new("₽IXIE WRANGLER"), TextFont { font: handles.fonts[0].clone(), - font_size: 40.0, + font_size: 25.0, ..default() }, TextColor(theme::PIXIE[1].into()), @@ -125,7 +123,8 @@ fn level_select_enter(mut commands: Commands, best_scores: Res, hand }, TextColor(theme::FINISHED_ROAD[1].into()), )); - // TODO clock for flavor + // TODO add total star count + // TODO clock for flavor? }); }) .id(); @@ -168,11 +167,11 @@ fn level_select_enter(mut commands: Commands, best_scores: Res, hand commands .entity(main_content) - .add_child(levels_panel) - .add_child(settings_panel); + .add_children(&[levels_panel, settings_panel]); - commands.entity(root).add_child(top_bar); - commands.entity(root).add_child(main_content); + commands + .entity(root) + .add_children(&[main_content, bottom_bar]); } fn panel( @@ -307,15 +306,12 @@ fn level_item( ) } -fn level_select_update() {} - fn level_select_exit( mut commands: Commands, query: Query>, mut mouse: ResMut>, ) { for entity in query.iter() { - info!("level_select_exit despawning: {entity}"); commands.entity(entity).despawn(); } @@ -329,15 +325,7 @@ fn populate_settings_panel_body( handles: Res, ) { commands.entity(trigger.target()).with_child(( - Text::new("TODO"), - TextFont::from_font(handles.fonts[0].clone()), - )); - commands.entity(trigger.target()).with_child(( - Text::new("Music: 30"), - TextFont::from_font(handles.fonts[0].clone()), - )); - commands.entity(trigger.target()).with_child(( - Text::new("SFX: 30"), + Text::new("There aren't any settings yet! Soon!"), TextFont::from_font(handles.fonts[0].clone()), )); } @@ -349,7 +337,6 @@ fn populate_levels_panel_body( best_scores: Res, levels: Res>, ) { - info!("populate_levels_panel_body: {}", trigger.target()); for level_index in 1..=NUM_LEVELS { let Some(handle) = handles.levels.get(level_index as usize - 1) else { warn!("No level handle for level {level_index}"); From 29acfde9302308e169f95560eb29b1c85478535e Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 16 Jun 2025 11:45:00 -0700 Subject: [PATCH 4/6] Tidying --- panels_poc.rs | 107 -------------------------------------------------- 1 file changed, 107 deletions(-) delete mode 100644 panels_poc.rs diff --git a/panels_poc.rs b/panels_poc.rs deleted file mode 100644 index f774247..0000000 --- a/panels_poc.rs +++ /dev/null @@ -1,107 +0,0 @@ -use bevy::{prelude::*, winit::WinitSettings}; - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - // Only run the app when there is user input. This will significantly reduce CPU/GPU use. - .insert_resource(WinitSettings::desktop_app()) - .add_systems(Startup, setup) - .add_observer(add_items) - .run(); -} - -#[derive(Component)] -struct ItemPanel; -#[derive(Component)] -struct PanelContent; - -fn setup(mut commands: Commands) { - commands.spawn(( - Node { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - padding: UiRect::all(Val::Px(20.0)), - ..default() - }, - children![( - Node { - display: Display::Grid, - grid_template_columns: vec![GridTrack::flex(0.75), GridTrack::flex(0.25)], - column_gap: Val::Px(20.), - width: Val::Percent(100.0), - height: Val::Percent(100.0), - ..default() - }, - children![(ItemPanel, panel()), panel(),] - )], - )); -} - -fn panel() -> impl Bundle { - ( - Node { - width: Val::Percent(100.0), - height: Val::Percent(100.0), - flex_direction: FlexDirection::Column, - ..default() - }, - children![ - ( - Node { - width: Val::Px(100.), - padding: UiRect::all(Val::Px(5.0)), - ..default() - }, - BackgroundColor(Color::srgb(1.0, 0.0, 0.0).into()), - children![(Text::new("Panel"),)] - ), - ( - PanelContent, - Node { - width: Val::Percent(100.), - height: Val::Percent(100.), - padding: UiRect::all(Val::Px(5.0)), - flex_wrap: FlexWrap::Wrap, - overflow: Overflow::scroll_y(), - row_gap: Val::Px(5.0), - column_gap: Val::Px(5.0), - ..default() - }, - BackgroundColor(Color::srgb(0.0, 1.0, 0.0).into()), - children![(Text::new("Panel Content"),)] - ) - ], - ) -} - -fn item(index: usize) -> impl Bundle { - ( - Node { - width: Val::Px(50.0), - height: Val::Px(50.0), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - ..default() - }, - BackgroundColor(Color::srgb(0.0, 0.0, 1.0).into()), - children![Text::new(format!("{}", index)),], - ) -} - -fn add_items( - trigger: Trigger, - panel_content: Query<(), With>, - children: Query<&ChildOf>, - mut commands: Commands, -) { - for entity in children.iter_ancestors(trigger.target()) { - if panel_content.get(entity).is_ok() { - commands - .entity(trigger.target()) - .despawn_related::(); - for i in 0..50 { - commands.entity(trigger.target()).with_child(item(i + 1)); - } - } - } -} From 0be7c2051c91737f35fcca7a4e10661225680954 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 16 Jun 2025 11:45:34 -0700 Subject: [PATCH 5/6] Add TODO --- src/ui/level_select.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/level_select.rs b/src/ui/level_select.rs index 0a0d8ec..e227ea0 100644 --- a/src/ui/level_select.rs +++ b/src/ui/level_select.rs @@ -252,6 +252,8 @@ fn level_item( ("".to_string(), "".to_string(), "".to_string()) }; + // TODO display level name + ( Button, Name::new("LevelItem"), From 84086c47a803a9a39d9eda085b8429b5300390eb Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 16 Jun 2025 12:46:09 -0700 Subject: [PATCH 6/6] Fix panel titles taking up full width --- src/ui/level_select.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/level_select.rs b/src/ui/level_select.rs index e227ea0..609d87b 100644 --- a/src/ui/level_select.rs +++ b/src/ui/level_select.rs @@ -199,7 +199,7 @@ fn panel( top: Val::Px(10.), bottom: Val::Px(10.), }, - width: Val::Auto, + align_self: AlignSelf::FlexStart, ..default() }, BackgroundColor(theme::UI_NORMAL_BUTTON.into()),