From 0a2d2b7270b387dc2b0c42a1e3e46171d90627ce Mon Sep 17 00:00:00 2001 From: Malkierian Date: Tue, 28 Jan 2025 20:59:01 -0700 Subject: [PATCH] Add menu files, hook menu up to window system. Temporarily rename new menu's UIWidgets to UIWidgets2 to allow both menu systems to coexist temporarily. --- .../custom/fonts/Inconsolata-Regular.ttf | Bin 0 -> 101752 bytes .../custom/fonts/Montserrat-Regular.ttf | Bin 0 -> 197624 bytes soh/soh/ShipUtils.cpp | 116 +++ soh/soh/ShipUtils.h | 31 + soh/soh/SohGui/Menu.cpp | 811 ++++++++++++++++++ soh/soh/SohGui/Menu.h | 65 ++ soh/soh/SohGui/MenuTypes.h | 301 +++++++ soh/soh/SohGui/SohGui.cpp | 13 +- soh/soh/SohGui/SohMenu.cpp | 578 +++++++++++++ soh/soh/SohGui/SohMenu.h | 72 ++ soh/soh/SohGui/UIWidgets2.cpp | 640 ++++++++++++++ soh/soh/SohGui/UIWidgets2.hpp | 717 ++++++++++++++++ 12 files changed, 3340 insertions(+), 4 deletions(-) create mode 100644 soh/assets/custom/fonts/Inconsolata-Regular.ttf create mode 100644 soh/assets/custom/fonts/Montserrat-Regular.ttf create mode 100644 soh/soh/ShipUtils.cpp create mode 100644 soh/soh/ShipUtils.h create mode 100644 soh/soh/SohGui/Menu.cpp create mode 100644 soh/soh/SohGui/Menu.h create mode 100644 soh/soh/SohGui/MenuTypes.h create mode 100644 soh/soh/SohGui/SohMenu.cpp create mode 100644 soh/soh/SohGui/SohMenu.h create mode 100644 soh/soh/SohGui/UIWidgets2.cpp create mode 100644 soh/soh/SohGui/UIWidgets2.hpp diff --git a/soh/assets/custom/fonts/Inconsolata-Regular.ttf b/soh/assets/custom/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d1241516bc2357fadd283371e34055d9b1cc89a8 GIT binary patch literal 101752 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�qnEPT11X=ckn@bDcNSYq1*MY8OvKB(de&^F zLop1S)0^yOv(1=QKj}+XgOso}``n|MSYcx=*&Vf7%d_RNT}^Oe1C#h3*1`gN1?Rrw zFng#uyVO`?J}ot!iIRMEN3%8e3jLEU01-=OwESIVT3n3{pl~J_`DR z6wh|gNlen=@fP1$Hdw*@Gqs3P&bcZIo9VAu+^LMaw9tx|?TZ$HdtA;QXF?0U8Y&0NUz(!{GRy*geFD%B;K!tOkO&*`kRV-!{<``{;Gqd$b zV@Fe`6yk1gvf%Y(k`asEo@yp7OV^xn(K~iTV-%2bU_o@Y_{6C*!IUR9aZ+veMdP#H z>_j#{k#SS%KE2eV5)fWpaxZ9ZrwT8ys{ypOVH#p@T#T?K$qnHA&RB0M?Lb$L*|6#>)~>M3Wg$uoMmz0|6vfh)N55%`b*5LG5> zib(Qok0>cw#g(ns9lNr9ft^=o#^j zw$p|6W05F&w5IpeTQ6E~iABe-!gj0KR6F_XCc;8q)TzYlDG$;>o0Oy{(p(&^Z0W=o z4sN^R13O{otx9vNM5pFq4hBiGWql*dQQMlnM4^l!{eaxxE2w@>E!Ya3K&CTkP{Gyz z^N9t!tZACTS+PUSlB@h81TsITPux32K{v3-b0>KDiJKy!SC?1*$gX0C4MrKp0j>u2TmJh1 zQv*Zr0slV0)4-nMzb{L_XIIIGpbUtzz*2gKCFM);JLrnvFUIee^M5C*;(5TolK&1I z@%umH-`~XHiK61)ua;kh=ZT&x(yOJX*%k8T_?@T;`~ZI`|97J0igXG7{jhum{+%dE z&p)lc4|K#nQ1RndO$vEr8%~{JdY}GagiM=q^XE-|>^)i+k7LiQ$|C>UA9OyrtK3T_ zdu8#pQ1<4+;heY3&iZEd(XF-9=VniDpcQ)V^jdYt=K5kTw^-l21Cm6g2+q9m&s%h` zMv;)?8uNEh8QdHZM$E5v*$3l$S8bTg74^-nh$rdu<(w8A{$eWlm#W!rGTNBSUQ6c73QLsNwVRP()Mmpu<)#rs zB2$QrWfGH}sT3?1nDK0|Vz5P_qep!{uQ%JY#hM4Rgv`%dbENx7M;}~kxe)DC!-WfU zanuHh8Jq@5k}&g!%)CEElF)7qx$?zWJC`g4&8~q$B+FIo(ZmA?F8e*{7%m{}N*+-daN^WY8xHSr6M($rx#2)G0q#T=bP-otSObm`BO^QHJPexHaXajlKbW;FTo;Yi+xY?JM4wU?NT?sOwwnx=C=)qR2c6DEkS zYSHLK_Mq3OG6)dE^`)AhHIy3zcMObOEJ25#KPoaBJV$`@YBuH|%AlBT`e5W@tYft4dBElMc2WL;D#kvTAb(#nHVlMES6!MMj0b3JJGM;wlT!{N7k!Z;LfAf>RUu(zYIzz_N= z0)4)6M{T7w`VV^>h6UDc)zqXt^<-kg&zH<@a#_-4zdIYj>8MKD=TAm(Hen-`?WWSx znYqPn8=rQ03s@|RS=LpSO5^Ji-drS*a+`4ng*OnN$mXU}XKq`*hyl;7IQjS!sKL-l zw)=H5<7X;@v*?WD%j&G#7!AuGir`;8E1zP&kUt6;mxYzwnGfL(N24j7Hlj4ZSS%Fu zka{{DG=d%n+Vfj3N%Y}I{1K4>9vEnIZ_eA+L>soyuvk-ac=1{D*3)vY&rcN;N6_Vp z2Fz*qR3-)^KRXt^uK!j-iyiu#FH!D)vp<{7w2FD0nr}9HU6E))DMmu&QZ-zPa>RiS zVg4k?+zlT)D6N~1!yPc%Enct7XEm9OR;w`>2)w`qApe9w;WL6@9?T*`IT%&6j0_Mz z@`ktOtJP~y?LV~^(ap)Pf3di9(JQ{j5yRyU9IyM}cTG#Ps8Q<8$L8nerdtye-R?|p zd~9rbe0+NSx^>=BpDx>IWQnc_@*-40KMYV3ROTU(+OlVw^B1q!##uR!Hq!v zAERw|6e7W5H@GaZxW5z&B|Rpa;&3G#F>fPK8IJ}+*~u2m@d=KxQ$Ncm_)3o_ixv=@ zMW>4e{2{Y0Z*+PrC}gtOV_sJ|6Uv&5`Bbjmf1^r(SL`MVK$9=8{2DgfD`Cy}V9h*t z{7*FN12B+^18wib=^@SsUDCmT@X&rPr1Zcy89Gihn8HD9Q zHl=2<$tcaH-!<$2fn74dj%@N0No|-wQQN@`ta5IN@V!55kkgGMt4%dkTIsY?c3%Fc5kMDdy!thwAdW#RetK}toj+oWU9ZCF% z7yM^4V)x?;U)vasg5lZg`>&4Y+%WFsvoZnxA^1t=Q6TE#$wMC^(wB#TELfLqA+>oK ziUR15YeBoCV|IFki33AZ`l&CON%~yzQZU~Qpan{As`*=+#@ah7N-~>N)@M|5{xsLj zr?X?}KsMs=2Vwpi4Ea)@%junQQ7KCXr*D8ureu1JgzOi`>iUBs0Z2`$f#-Ce7 zGFc}iOOrsdnixWoN%u1k_R}@kPp11IT0vXPMbfGFq>_GXw+mH3Cr?`4S)0=TqnS76P|k&_izil zWc4cy>Q2UJ>|{DE{OZ^=BwmdT_sv1Or0(vCqMXwSLvZ+ zR_WC%Z)fk3e*`IOyWec`Y8a^pJ_IPLhtfRuhFzC!SpTY>-p2NBA~ChIb$;RMJ5E3S zwkz|q;jL%BbaL{gFWwd*ESE9fTh;Nbwjq0yEJ8#Haf2H1b`3U49SFAh+GG zr!%kwXYIw6Z`2ZYHH$y-qtoTT%pAF*dldADK}8Dk8anyV`U$#o|nQNot2~` zzlC&}!6)yOUJC9nNWO-QQ8}Gt7cNL{?RqR z-4XW`vNo&R<>N*ZjE>wfp8$^?>;U_)d)YQ_%r_-LehDzh`K5>~rj9C`vnap3y=wz0N~Qg*GY5 zfcW;4_q@Gu>ESoN`)&Bz`%vJ`rB~ii6yHGcC5-W<)iKa4td4OWSGmfF_{MU@!~rl)Zcu(|sHZs5|j;R_AY5-%I;>7?!_npg5FI@^`3% zW6xa2-o`GJZ`J4Jg-?*CSvZNFe;l8tVegTZf~ar(G0?9?*TiIMy2p3OBe>2JcVNo$ ztusqw)nu~T*i8HN5s3=$x-VhSeYj8(-aYY@}ng z>L5J>uqkhKr6GI!S)j9SCRa>56Uma8dzY#*bMRrA{Ba*3B{n;%CvjjTsNNA z&gI%n8G%e-_pm$rXRzo0c8z?Vf78!?wyFPzNx6qSuO24=UN!PDc{8L4DvE(sX&%P~ zNQkc5O^E%DjOg`duNczlcC0X04Mgg5*-*Xc7?1DnZIYin;_u~7 ze$}qc7jCKeHcUqKMw8u<4rDf*-J3ppW8MQ}ya^GDTXF>Bd0S1xXDT9$>VS5e?!NQd@c92m*Vd&|9fiw?`PSxfodvI4F;+o$aHz@jp&C zBHGTZ{2R`bd;_A>bC6K$=kt|y>-?E)c6w~iQJwcJnph&>^e6mR`gMLBb7Sb(?QMzK zq`Akah5-N1o%I-1E#fEoLv}yee|{hOIs*8S+>=hb<@Z5-85L^(Kzm3{SHO>CAsUso z2<#RLZ4v1GO?IvwDC>3Zd^I$_m?>_o%`*c_wrig92pci`f)VG`=4|n#@nkKiRI^j@ zqR&^X_jKNLa7O3QdqPg#4?X2>HrFc|a8fms&BnaWm-J7_juDI7X4ZYdGuj=^ZE70l zE;S^|h)G1Or`@9qrhMKMY%IOB4&v&Q@|B=X4i!_=IEBIGot&JWu7R2`ArDJB%$`h# zIxjfFjdC>YgQ#y%`Ht79pjQkxxhrdq2_0k{#6(+E;D07t*Z=&XXuA@QR41b0Qo`JZ zM3W6&y^dLv#h^Je>QyS)U@l}2*3uKq(f`B5#JR!7=CS5dO>srNKQtKq{Ut}tfk3F) z8+SDe>k6g$JmuVLi+T2e3CP|QVh-O%1z3mrC({l`&RWsyOGYdHNHJ`ohE@n5+9&x+ z@>!AHU4SDsj#ItxO>H}?>FGoXa;FRipMr0z;)!jT`iJ$FDSHvU!1k7{F~5{8Erdg6 zi_MpgMaQd2^I!Z=GWN~DeU`>jS;?i*nX}P4{*sfX5UjLLP48?4B9Vm=x5sJm=BGTJ!Mz4m4Z2M{0*yp#P{z71VpxK(Y`=`6ThQrge7V-t8=h+ss@bK#&F5}aOG z>c}9#W(gpu0{$bz+-QUYp>xRlLq&H>{b)K~=m-S(W1W8unm0Vmmkhd|=50D*s zaqdIF$X9_^WAG^=Lk5O3{Hkl)a^Vz9jL*)7%Dr;Jo=34!qFD&kOG!`lIlW%;{&X;c~xt%ywx~lVqf-YxfYIk?fU27EZ>AO zxt8;Ux0V-H{)hbsZZ|N<`+1}m$2g+Soivvbv4h6EpGwap1fGyVl#u7rFDr`EM98)3 zD17SHQ6AU|(vF2nCk%5?<2ePF7ef{)Zt5E%11A$=r}pp0-@oBb@APadr%dNJo-@r3 z^j_8b9sc%j1P*2kE{`?`^3%oXy_1loq6+X|c@`!{o-Bk(C}egry+a1RKI>R=$*qLkZY?emg5B~u?KoyQfSPADyFT+Xck=;wxZq@HE zl`5#|u@~({+c{Nx)n>Z@Vsx%Khf{m$}!3l<6iD&Hc z$hd{!x@@L**zFOo%To+oa=Ym^W=KvbF?4*6Sj<6>%WpONmC;I|7)mv~anyJkt!B4X zaff0EicZ%0&nEL8Pd?$250n;))uqw?S8UO#+NFjmi)=JH-2ubJ*#fFR>n=BQHhvNe zw9zB#u_Nh{*<;d4a4vksgZ1=S}9r5Fo{(TfgiAUsEv{csd+>q8UEK9UO9nGpUSG{jQ^n#zAZ_^_qJu z?d%uZUH$*C?qt8%;p{(dj~#QbC40P7+F2^~uRPubm%tBEB=)$v_Y^59C2?lQ2=<>J zJ*RU|&h>o4_K-djf!i z54Fwl!{$)P7S=EOIS!yv_=!ikOAFQ;@J2hJav{G^lCEAOi+=odRer7K>tCbYD)~Cx zchNtOy;I!o*@~}^7dACg>&MJ=yCh5WdT)b~&u~rzmrXdAyFBl=CX=R2##5iLFNZw( z<LAM?^Z+H>ZVhp@X8Z-9qQqV?PH-(NQp&s%U|qOWSB$3h<9Z#g z0L`t7R@|f2NGYJ#U-b%&A7wi@vx&hF|PQhOkH1Pw(cEEblpj3XJ<7O z?)YNyPTrpMb)#dxP#tMXs~hI#xS}}R4(}TI;*#?4lAI$(3%p4^lCC5AWrv=mE8>!* zcp+gSWk{~V_Zq(9iA2o5oH0FfhHqc1eR}Zhb6LDt?Emzb%brG%mnN>dh)Zo&%|vYMjz~R*jCrIMi`MDtBB{0V5_XTy4C)+{=RdE>mYVPI z>26Z6W<^PlbAi7CD3~(1L08Y&(8d??Xkb-3ANT57 z;ZOS>LbJTyB0YkDX&9b@i(K4;WTL#~^4iyuJ+ldTeoUsbU7WBQps8JccG!YDyePa2T@`i>BxzwNU8NiP z83E3FJ-9&wmpphp(U{vE2~#-pZMfDU62-L+{f9$dUzmO(yPzsP2s(Wc?=vDxq8}My zgf-6z&`u$-gL!fhb8WBpElii<1X*ge8vP&59Xpml19%)ZQ3QyB zxkKpKWfDT)=%3Cp+SlKY!M@tKXVaDMde_{XSQR&-9&t?3lU9BY9vTkzRUCM4;CpWC5Rq;;U4I1}#NJ$?VKZlCwCeX!UpAD6*Bb&8^%hcbT<<3;@=Y{R>+ zockQ$&O#LN&Cr_$@IUGsaM_&3m(Wm@bJkAgzNE8o%z!AX#O1z5j#+s5S5d`s%XbR8I zof);Z9BCc;mHjr+K|~@Ho$3u2| zIOg)joUE3eO6N<(LTW0P!=sf_J~@>yBs>Ylm-Nu>H%YfM5yZ6wo+M^vhgA4A4qwxu z#(`&|u;bDWCIxA;wZILS4ulw@f2&b23Su~Li;qYj@N*Ust1E=rmqNddn5i+Y$GK^GasJm@jF!CmpC! z%x!quV4O7=LlYJGlKE4L#*yBLAo;U~)9H9Tm9kkq zE=!6chkMn zD;})P#EVr>p8s2L0cD zE&t&N-(iK7Qy~f81%83lFzJj&v#hr;E6SQ-13`Vj{pGmqZF@-|r#fC8)N0{NUqUSU z=D-)e>SveS-NM3y zw{+}{*i-zjUEyjCsuWRaNre!FsBBPzdic=jH_UhuT%0$1?2eeh5b>77O4hHl7VDv4 zHDh6SxtGp3bE|*l|10lHprowse82mBUHiW8Rb5rxRn_}e-9^(4UCoXlf`I#yh(rYu z9vW!`CM^mk5ranD;x?1jEGjBCCKASDOlI;l&d8WV9hVqq-WZRQhsJrs1N-au```P0 zRn?1Ql5=v-ob$TrD(b7d{_lUk_y51@2hF8=XXE@fW>LQ%O1Z)gdrv$DF(1u%T=k)N zfB&om)-Xk9Z{$0m}^j@CS;!>yZ|~BJ&m-8s;FwBZX_&{n(G-F&kXAlS<6V^lFC4A;7hk zJoLk@e190$KXi*3zUOo2x>-9rw_p7xb0rbc)RRngCzCx1oKtm#JdUu-9rWpt{sX?Yk;;tcORJ@I(8ptI7AydcUa;J-b+EOy*Z@~N9gwr@;fI`6 z@^DPfE;tmnVM~V1ILj*Q|MQ?!t?y?qs9%qF#_=!F5s!5wZGML%V5iq0ZdtAQ!tS@@ z3v0mP47^wSyr#l$XR*3jsteL4Q>uq+7t*GP>(!!2;*pF-QqJNhe#0*K#IHztHc9KC z5gGU{a1SD~96jAltsQMfD;RH*3r_tgPGz%YW-(K$(Wn1!%kuvE`u^oDcu%MCo}JSX zjdskzVH^JLzWVB>EnC*nH+l(rFT~pCX0n4+1cGU9dn~oRuz7P~JMH5wX2-zihOm!5 z0`30q@#JFmd3J|}zV-5X5oZ*R@M|NHcQbspMrN>{Kq#L-MLTY^^D6r{v=c)dDi|}3M>5cnBwSh| zHK)Ce7| zldX&nw7b<`8lPfEJJm-I+SNDR*%Ap+z$XFtoG;)bTPy~X*^CrobZmo;kpMXKOa3qq zY?dsB_1gRm-46$!Zn;K2U!Au<{rmT`6enxLcwVcF$7q@cpJ7%C8S=gm?Bv(fN1igO zza#*$mF%ecC_#v2><8H@v=jn_expMOLtz+^QGz`(2|~kxG!~0Cw%Kpg!(te0i9R%% znN8ZjUc(s>0iilYwwQ6HTn&WQn^?%ClngkM{931akU+#*j8A>7ldU>vXBizrRL!AkotnLB(1wPB|7Z0P$B=PpslFUvjr z{lewUu#UYu7JR&W!-nq1gJUh%T*Fsm7FHvSzC4GW1|uR(ER_Xa&!Nxbdk9TS?1z(T zze9~PMKrFSS_fH%(^|LzdM6-2URRt+v@5d5j$aqdUap{3_ENCIS0nW|C;(30JZ2Gq zq1v-=!wa$JuQefNKH}a( zmQd|n@@0?7;jq-XTr_r9S`Qufd&d-;mD z02g)-R)JQ7u)I22?1~$Vh>T#&%#U7@riXM1#G1&Z!NDzn`~zy``75UNZZ<10kOd3T zdnc@eMuuPZGC}UGE8Zc9ZBlQa-g}(|>u%8&z0&)79V9v^UitxF1FA2nTi8bvDM*}( zM`aaRfgh<`9wlxmzJIauJs9;WPufG@!&3aZUP9V6QwiBI{s+ATYn`#gk?{qU68}6? z31#EQcjzVLFV9q>E_&*J=_Qo@8B6^9RH;(p-I+=lMoxZ_O0ZV;kupOGWG>J~s`O7Q z!QrL!>+q6>rnb=I8B(|KF2pFLUDHeKnq1<@^b$uVmpD1S#K}n|SpU=#SU24Cq|X&A zxNU|K+a{G5o1w&5r35;h3XRpk)RX!^7(>~qC5BOgjm@1jR_Wwa{6H(}77ld4jQ2CS zgx-(7F8FpPh-eE}v-$=N(>W#+U5U|-FQ{T7=4?VK?c~&I5Wgr#CFLX3Prou|hQkw*Lc?Fu}BSiKLt|joT-&LNTh%6>U0~Euo zP&j5pqttpwS#920-_4Ro{Stde`3KO3IO*hr7C`5$vr-;@jt*Edbjo2!nhy`wGGrZC zEu9T7(#6p9FOxP(n-Sl-P1+%S0l9>3!a48#$WnQibg%TF^bP4dl0_g?v|G(E#UU}BavsbEPcsXmO z>i!FSx^5-^x%4mh27jfeli&<)|9%LZUUf?^R<{TF&4 zuKgM57-|1p?So-@vb*D#?Ju z{TGVf6l;8{#XZfoCVjWsP9DBnKg@gW%E&rj=ZnkA1}M5JEa z`uLw*E5cwpAQxA|ygB*y+i$b~#0!nET889~0r@hhAQVhd776)y`TmCUm#1~OzHB_yNsBGAy&5NkNu;9*e77Tw95Bg}cH zD$57Zho?UH7i%|PIKAtVgmXUEY9}6FcG{R>{lGpwO6C#}^bQvbmh`*Kc zf$=Nl10y3N$7$q%3Dw|w8Lj~$uZrEourGx$XWWC0-gTJJ@`y%A-1A@2*qvyL{*I5x zW`fH22>}slggwGF1=^dn!tO^BBCSEb)@TgP(A`=+o|SCxh$>Sn@`;ac6)h1&w6W28 z`45{JlFh@M5rg7w(9tH$i-r?eG{W|(A7UuVmT~jQaWN1MRXz>@kvhH+sx7gY#v=48 z)JLw%)(I@pM(%;;0+{$*0f)nDM~)b63sTsn{6N40(hZF&#cO3LERe%A(G(;{?A2$K?edzrg#D zj?!nXk4f-DAH)jOElQDwQwu-$lSnF40iT#ZM|;#6Jbp|}LMe_Psm_CQM-Dw4FR2-y zfSwp3PMwM|YAs-tq6sb;I7cQ+$dA(xlSG5w20@X;j1I}`G$~V&Fap#1AYq7d(1wvS zXOdVPXK1%wgDuW$bNHIU*C1G!1}9Myc~z=HMPM8HrqL6`=&U!OwgolEPk{dpAxdft z5T3?6;ez6J*U+wAyAB=456Q3H6DhWp`%HMOpg3~EcZ~sIVlKN;@NIJR$Va%lN30AZ zO4B_?q%ZK2SlbgIlPTDTR3SuPRm`oS-buK(Rl^IdypHD@^>WnH@nR+n0xfU~6bMA4 zkPdNN)5obvR7g`yUwqF9-=}`h_Ex^<^@#^5MkE}fom+C|kw~RqNI-)Aq2q%igE;Bv z2IT)mgFNXf{=;@0=d4IC#2#S;`YPzwu=~P#{>xWHtV$ht$9-6pBYX|1rBvAg-zOrC zC|@RciRSv`nO6idiN&G?(-)1}LNi8Wz`bbdEAgkJ)1gBbf&iyz_6p8P9%8RlW&_Wt z8L>|AO$snzHH9#*G3Mi&OIu2twTYmG-Re=y;%nM05Q&N{7D5Eh{i_2e48u^5vXc69 z>?O@o|DOF5{h!tv*}sTW>nj9IBkch-IHKumj1qi}5M##Ih&Gch6CTsR%45fm9TKF? zc^qa%EfW8R3z>u0$&^CTy2zw09H?<1fFSaa@?ghX`DTQ z-RPWirrI`ZW~0-M@W@T(`&Mf$XVv*WT1C6W_c1Y&8^m1&-xBA$194OplIF?@5L}Ojk`J|_hDwYn1GM8syywQrdEN^=#pu$W{iWi(hZnJ zToJrYQ%9;?k!VXu9KqW#MP5~-Xw1(-Hb_+@aEiq21Wl68Peci@jDjvTZiHGk)G}Fd zMYoXlG)LmUydLjQ)YM_hg|Swfj~2Q5wOu)FtYlXU84Lx3)&l+MGDyP-8flIACi+J$ zzJ`1S4-JygJ_24md7P8#kYO`z-MO^qBClH0G1CmSnphDok&hkIWio-MR`OpC4rYDa zXmSyeVx)-B=W*&ux=HkCvbZq6a%InjEBaF--T8&ZY~1lc0Q>7 zFhUg&gaTB+8H@ny6XjQ~q`I)Yo)t||vz(5Pvp%em8y1k&f(53e4Ok(%%U2P1waC3k zYnxEsRp-_;L8Fkc=2{!bh6^sq+Zd*!EHnxnJ;D}BU+qNXTy-Wmc}ddqIHV`ZHlhe{ zI~-xg0Sy|9&bdi`!Vcp$q;wSWv+uu>pGQ$}lKdne?#uA<-UuE9?V@a{GvS!3nQ}bR zc`c%&RYAEIF zS|%tDT0{k_rs>+6=0n3EmIC~zm9H6TRnn8e%)g&B6a9O(SN~pU3W9HPh9)FEBqIHi zj)yY^dqJ&($GN0G4oNStM3c|hnXIcrM+~;fJs%EkF4bm zI}-Xza0p;~`;9k$;eHb@f-~!ap56(==;srKy+#?6;iJiVnm@_<{2la9eP$L~lh8~4ifRGLN0Nabp?nyf{oV$5oMG;p!p&4o#Mt~P+ zRLnKd8V?5PZp7{3wIHjDl}9u&gLIl==W(cU`CT;=tFnz_t}yB4Q6cj=x7J{Tbeen) znncrj;}Vp{iK%dFwlY?dFpy2Umx=lzDM+iJNkZM8g>cao6J55=EJX!{35gFH(l1XqM}vLLtQ*HGZg#6<(x2Mdz#4PcTY;m%x_E}k9z-(s95q0@0>+_yOH(K8_@CQ+o z?1ozIpz$8Av(}&#=_t@5sZNc@gZD(nfhK!&i=&3GP!05Nt0{}4XZhashEG=Iqj4UkSmisy%Eg>3 zTwS-#1h>|VGkAO?-J?!)0MBa;DRF`p7Il@TeF_XiCA{Htp*#e{qxr1 zCLn`aK+7Nxa8>nDcADP8kn&menfWoD#yoy*l&#he1G98%L8rbz=) zQ?rW2$TdxaQxwPfsS=Xgwz^e3RX$Pp&p(kDh1LHziG)3qp%wdji)U0NQlXsFMJ9jk- z4CJG?A=bibM!d8Ezud{AU$8y7bv`BDD$qi-#?QUz8VPSlF+OEPuZ%?PuW~8Q5pzw zY@G5I*9Q63ruzJ}Jy@h)_xb%TAx|n|i6U0P?(^C4_gC8cr*F|7NJUf8h$G}m$73m8 zm+EVOQ)XSl`J6Lo)`iXmvre1)=ggWS=|%7dMST4Ev+fH;Es>PRorpX9zFC+4$tz}n0cA|N_q?n za_We@!PyK2P#}s72?50aPfR%~-%QKsh$q}r*743yTF3M1G5e6s5q4tTrjK9dk^BE9 zp9{{|gg6&VENn|8kf5Ds*`#b9w4JgZVN;~N0m_7v&E&HpH*0aOp%-V96GKHs z{gF~C$J25&-`3U`N^~aIw$!z(O>`x~xsLW+_~eR9yX(AO1=%6k#SOj9jrq0#M3N^; zr9^KwKhTzMZ0YYgB8F`sYjEQQ zkKLOyk$UB#lPA4(o3`DCLioBhgMa+nL`KxiByQWbsm=?{2{%I^6738rE%_c#L>x~| zOgz3d0w&kp;(;?|SmZS@1hU0;LnXMT2SI4;+WMXpAk>iy<2qk@OBGX@zLYgvlFz80 z)zG&V%|U8YD}ry#~4G*@BQekQnJ&3SEi z&pAImx6~$YOI~{DzTG|FEpF`Cbp5>~mGFa158DxU;Kf-AixmwSxvU{9rwn+eJi-ld z={vh=V}9#Ri_h!2yn8|V%(bV>+w*%~e{^2wA3~~{$Xv1Sdla{!#YrP78%M4osgc!; zeI$RL^mtgnR>ZIg%NC)g6k{67@pPS`g$v939z3t(oI7v$;Eex~T@xHQqiFB{%=f-^ zPXDU!9K7&amU}$1`MHOdP>XdF?5yQO~PS;o~XZ{B(Bz!}YFu3B^U#dG^_y6@4?7QU^% zIZEwQoY+f%N4GQ=dD;B4l05;BCy>Oil(8yXTO|ucwpCKV^4${35lY#nC~^$pGC1C( zBkNo_#KTF&!5VM^&izOvRZ^}8I>Z4c%$vkJbt+?)ow`FP3Y z3)3EVmp|}3Z(&}i!QWcnNN1!QaCgkxSc!Jzh&Wx!mIJJ(t-hFY&NUkr1(mHG2FuoV zEYlFChRM<5FVy%M&5I`v6M=CQbZP=>m5`Q+&?D4HNsjp4Oi5YX%>-V@BgfcZm;8Nm z(`}0j-gHMi+Yrcg$D0N-xGycdg>*~Yyf|(3CWkn{AJFs&DnQ%{AqwRiIG!%9y2D#0j&-u;|BC7UbdnQ-# z2eL628?r>xS$Db(ceA^5t-$p1#1TZY-UDy2dki#VMet@d_7ZMfiXbI}E9?0~<2+o? zaODNNW-njB_R5K_azo2$?c-Vbo8uP{jto!yPh>*pG~A-gXf ztgnx@&rRFiW;?DzX+cUdqdihU67#;6<~g#_=5<=_CVMaeWQ#jI4Q)wJG8QoUXEo-A z^MOLVTQ(|=h|eC#Alpqi?6yVX^=h6=H)Yf2xYb3{CU$;X-$$Z=tnVC)?cSfX$hTdm}BlsxM?}4a{rqK6`#+Zr(ZF zUC3q_X`Qn)S6EU?rTUgMH7)6*_v@M$H`Vtx1$&x(uq~W6gTWeXh$K4`Hor4b-*8!B z(S^m{3l|me-rKUg+>Q&`XRl~!J$-g2J$pqP-qVytN1wA3ysA|yBSsc^A7qCsWH6O7 zH1oD_&CK&S7YXZyOFR>@U<-)6XE}c8C{i4=lI1w*DTFf>yHg@%W+VpPNwSzST zbCF1^dDjC${2==0kC>4xd2x{L9eD>NlwWEo2e5mLa||}0Tk(1&7lo%NIEqJ!q9M@x zA&cN0PfK&V(l#&K{jC)xz2%dWs8R-7d8eA;Phe*!d zj1n8LkiCtAZMptN zuctogqQA_%j%;s(x8?k$)`(Vb|H=F5~rvRs&NL`tzigT-omt8Yo8 zzc7&NU$-)GZmKQeV6UZHg5IX=KL!Vu*TBIC+3a&EidH)gJwIikjD^d4SfhPT;0ep;di;SzLr?5PO&=> z(aGwz+MpehpoEx&#`-$X|9Leo#zAr^KhJAc^7wFs0ai|pq6(`=PyeKe$IhXvg7ZzC zJAzMt_bf`7Pnq-a8bpAw9Tth-VGkfl5!@T6j8@zD4`h#V{C8|!bMtADwq&X!hP0cw ziJ;jT^Z8>gSIqB=IkC|==7n2q*$s>NIRfM<;-U&W~gdSv_kQn#% za+}l?`1rK-#lpN*h2hT)st@=VBJ&`kb>LQ|79jm&A*e2Qv{K@0R=&9BFzECE2t9$=r4r`SzKv>{!gg^1BSS)te;)XeiZqkY^aSxrm$l@47vOnOGD&H_{T&oaZKhF+`L=|l?daK z5Fv;VI%L2>(w|CXAjhs@gsqiKc*(O@)CF&ju`9>e67?yza{M0HYT;uRjR zN&CMZDJpf9gSd6Y7LB?smXO`;`PSk6`wMzlW!*DBvFs__dv9Ucp8F{T^8UgD$zQ()X{12!TY(!&Df5sC)@{7( za(g{av(0Wbo9axsTb|M)BT*o!eyeejR|$V17!iOgAZyCc!Sw-q3#?iF)y^{`XX^iC ztGb|oIqRSC1@$GPuNTo5!4HaaIpFmNJia=&)#7m3oOawIXHXz52ra8U=5fJP+SF6i z=UxKkjrTFLdScVx)pS9JAf9t`qo z`VJ#i5{~VX6@?TSeEmMl(`fVr6p#Qr`&ZZB_p9BHJ;okrQ7>u1YYS}Suw-Vhv*!^J zHO%9nK8xt5#pQHn^=iu{fx!IvmW|Qq5|77{UAgkY&d&b+H7za3pG;R?ya*to6UbTZ z1s^pQh$np8gJ+L|cH5g3ED2gh0LS+93w(?qIZg3EfnEe;_yw~kK>1rChWrvMSOXL9WJJu}H7@uqx6N)gKh9?J#S#=Jr*; z#TQyv%!;-|qeS6pDa~HMiXo$yG`ox|eZ5AHNkLLk++^)^x!o2x04x^hen1zVN+?W$ z2}sKHBHnh9nB<@x=Gz23GPnnE$P^oFf>WNA_o*LxZ@$?-T6jKs`T7W4DnC~rKkwe? zE$7^M^g_p#TW#m6L?=~?-;YQ*x@*^2YE2;$f&w7~DdO%yasePQbH$6)16hoOD%`1L zL<0Cyt`FW4v4TzoSx4U{Z-Ltx#9h;Vzsu!|$9#hwiPnrePWKAh94U`C>GQ^8UY1R= zF!w2x0!|ldIs?~5Vwq?p8*T4%C;UNv#h}@0i-&`$Xgn9=)ZHdo43Dy(a84rdeUwT3 zKta!o-TVR;J+&B5FIG)X&h}OklN+EBS97Y9v<*tda@gVb%Lb+d+;-fG6fk={ZoVXL zz6u0?z816+fDYcE1>!gh@^@hi!sH1-v~#C(ZyM>>PpJkKEAc^e1mX7IyW+ z#qYeM{`|?bye9qRzs`9G8Qx^+_K6=WtC1guGQ6kDE~^Fk-6+X>8R^1IbT%@IESKcs zb(1*TCNhRGBo?`y9afjg^7zN{Gq;G8Wol_TyI?umEMta8dO+8vJA29daNcK@4}K`x@EL!p{1H)r^}sZT?te zBG3iS#iYk^-$xg02@l${JyW@gg1k3|CnJ0ho=HxtWEkRJ-(U~xOm-#kkA*U>sEa)t z?@FdR6YY&*cg#cW`~>ZEYV9!2$+b+Sf|Y{eh4b?{cf^$mG2BR<=wi=ydSdQyV|$`A zmF$Y6m8ax(c8UB9&GB(ua!V->F-NT12mR-tA-8{(dioWzPNk$|?JO5UA7-hgOOL*e z@83Ya#s-X}X61+J`-a7f*^gf*duNMWgdKJd($pe706sr2H{p(pTk-h-|NJ#}3b^q- ze7=K!eg&V`@y~ZkAE5kS0nP{Ir}6z=9ER)I1iMXs7}N$IHLd~10Vg6tNM@cZg;Wrc zwdLl*&EcC1o7qHW-@eRM80+P*yzZ0_ikjp-Ho)y^;5Cu{lNiEkQdzS3Tail!b>)Ls zU4^=+d5s)px5{6EW{}(BZ)6Nt@;9Fo*7syXyMsTB|_4V>x>79Dkuo^wz&wIW{ zwlUB%^h)5PaWq6*6vP+drz2M+ymvxU=+|VjvL^^R=68D?j)<|*+Y}2n2IXr5A$P!M z!nTUnV{tm|{#?lCO;djtV=cdowah6ye@eFQ&h<3kt7SLaFW<;SQf1#`hT95*-t7gHzG{AvO$< z_y}f5N>Npd`iEq=!lcxHpTeXFlHZ*eU>Flt?g74XcTFT%3tFWUvJ!(TN9`^&k#`F& zRaj-@Ar86I-HCW#J{D>8C!gpvgc>6;sQw;*OS3nSM61^zEB&n#KgD>j$9PM4vSVUB z+dgqWp4^BhotVLPmISvwn^QPnCyc*09oUZZhspka8k=^z5)2%=T}jp{99>}8+J65m=c+u1GhUQTJao=mA5rQ=vcX`F9i zH8h?@7;~y+S$%_VAX`5Wk9#|MWqUr?mMmnm&EW<==B;rp=$$dibtq^ab@Qfp_HN-$ z)AdO1uBd37$n@6~jW&g$cjFZo_IfRGf44g_SANfFRq%?cmHF?G8nJEs43U^TYVOiGO~Cm)|1qVgIBw<9nns#i%Zj_p`f|&?G51%0>7T zDYz@WKX_Mix4b`h$-0I`X!#uZHg-UXpe1ZcNs}M7_yq*zO3M+ihJDZ#ogN*?jVYL9V6*0>MlZsVV52Yg-N+Uw~Aiw!b9p#Pf}K{vBn2p2N9h zQg-4L<864p9?yTSG|+Q!T$8c~s@g@q9;yRC0I?vaa|l&>Dg!Ckg!Znbq_2qd=Dz8DvaYppc|eGKOo;ZNf4{te`zpUv ziI6_1ZR+Iyxmo>tYSSkoy)Dw~J899x&Ej<&5?WM591s${H)&6FfR#Eb_2Y!AGyLNs ziM(4Br9A#b3wa@px?A04BIMDPaK|nVb$=tAf3I>mXY({kh2Ol{|8P;t(??o}aKWKQ zm^zCe%7FXSk*k5L5H`3+&w(nt?1AYy)CJdF@zzu+n9-i<9ux?`sGzeD>_J?xT*OgE z_8M!4<3`0H?My;x1dgBmmJwDIOU%PdPPA?uN63Cc{$Ry*6s@Vfnb7&_I+I4;=HjQ5 zhL(GrjS%HQ=m)SxOlX(1Td3HN+EZeym(@dwhn}iukSLT^(}_-XrgL5Ds^$eaR~6uc zKO?>*0P*VhGZIJ=5l<$)fP3T5NDk?b_yGJF$t8mjAB;aEL&ylkN8!)NXtDzF)#P2^ z_sDMGJ>(1EugH1eZ^&if@5nE}zfyZbsRMOH+c{BR#H-OT;F`2PZ~{%h`9zwGcnVEH zya8>3cr)4z@#eGz;;ra%;FWX(@Y{4V@VoR~T-`!HMf^BD1N=F?0sKAv9_N3c4-kJy zA0qyU2|}61&<0FrXc<;{}E`vDJ>=)-cDL@UXWawPB=IUabT zoCZ8gVr!i+J;wI6kB!ZGG*0yoeb z0jrV6^=v&GeO~=4)Aec$3oFDoqm_%}m|5^XisOvfixHMMCy^q-5*MTj-hFWzNy-b& z64yu-e#H_u5pTZV61OT9w;`c?hUL6%skj|Av%Z$|_9UFuw#3U34_2`_j$+#~M@t+t zBr{p!jJVQ!mN+NY^rj^)o|m6WVoNVt&TIJUov_4pVxk{f;wIuuw_4&>$j8z?)SCFw zahCHoB%C(2#BGT`tzwDWksxy461T^^b<`4fAi-pjCGLosG1n4zBB3PK5_cy4#LW_S z!OUrGiMt|3NK4X>^dae_FKIvqBFrJ#B!e`;*}gc=BU#8Z7v~3-N*PGvkuDwSdjDn0 zM9iB5jU3{UXAa5y%RByVx>hJ#AC&g*rf-7unMj|HyZ)+vEs?edQmR_!{$=XM$h8OQ ziF*E3zRz3Z@86S(oU`#Zp11k)TuSO*68>wOJg@WfCqBRb`MHv(v_;MTUTsu;2jW{< zo7BKR54|B5clO7*JoMH)#B&h#!`U9V-i#!X#-#WudBlvP;vQxutq|*uy9OI=orP=J zxZ8}oRZq-9%~dbV#F>1=GZD{4d>~pyrEP_N+ZbUpJXxhJowKUl^Bk( zGYLTn6_`tv?9b0FDNQ<70_nI<^@wi3>e+*Ftjb}=IrZE)(!z)jM4On6{_s!R=g)1b z)*XXUx?vb+L(vKf2cbr4#0D9yRRb#|)l0Kc9yNN+q_t7PzDP63a>vX1WFSU!=^p-K zyF?=YVB`O?yUj1${XBIYJijs8q0UQ3!i!J+a~WRtr242wf7Dv_tl|-7#>^IscNT)W zs@|mD!^`e8ljgXqAJ%nQXt9Av6^E8mZ%g$^)xZ3ny`elyAMRm!pZR#hCGGrT3m3O< zjh8;N8s<~ALRBkeS2fETp|Czj@F~TCj#W4zb{Gv&WE34uXVL=t7TrvD&=d45y+waw z6__UrWMQlpYtIVU9`*q{#=hZ#+j0l)%qw8M<;R0~2#@4*_&UCa?-%7oWl>wy7hOaK zRzmsWuqcu)vXZPOYj{|BIC!{wRPylhsOk~mQNyE_M;(s_9<4k&cw~F@^BC?i(PO4Z zp~rlWB_0pWHfBe28FP8FhuPcgV-7ZlnQNO9%q`9R%|p#o&2O1kn%9`unKzlYn75gC zoA-LTc)59bd--?;cm;W-dUf_%=*_$}ZwGH@?=s%yy*<1Gyi>fpdS_LAtLl4IcU0Y5 z^+45+svh(0}&NO6;Vedh;E{n$Q7@N52d3lFRREP50i(jhpR_L53@%V z4}Xs!j|h+29`PQHJ<>cnd*pZw@fhPV!=u1xhh=6R?ci)KYp!TE8|@Hkj~(8{ZW*DSPyL_0X49m+jxhs>(Ws&1*ez3QH-`>TG4 zcKCkHv_C)z>X}+PXvi6=If??sRTVx3nfT{l(e|SLMUkb?@gMnZewBa2FYqt;=lnFQ z!W`*S`Ulc<09+`Y_MgSSd+!RUSVFGXD7o+2{p zB1VZZVw{*F9?QO12M?1I;aU!Ic}!3ZTU#8{R^sZ`=U_RDbjWeDwnbf2KW(#t zduf%lrdky(56?WPHPZsMAg!`CO)IBGX=SylT6wL4R-nz)W^10>0km2r;sKgA5Z|3- zeEnJ*^La1KaD&KLvJ$lE8uAX=OAezqd_%5)8vRiV(R{Q}ZJ`#cJJZQ@g`z1zMIHkU z`8hpLZ_}UXuko(eQ?U3fB+iOx}XpJ;y%|Ua~%)|w=q%|o| zoJd(xfq0R+m~BHbCw3ui!4K&G`g{zTNyd>0WGR`Zy-pUBW8?_=7^~i^WEHDV7t$$o z7F|Z?(Jg%SyR@6wPJlRyY?e) z^i9I)RN_t-6HmGdw8}bC1?zD$`kxG@_r8R`e^o-a{zC7P98eH_>3!0N z{zm%I-$@?E>JVB)hA~ElGS&G@ZbK7BfVlm?NdsK&_jk z4XAYod_SC|vn(UMK($wql@-;F@2WrO_8Kx=)|7VAUPeeO83gJ*Scb?@86d06FzJRb zuvz+wd9s$6FC)bQ86_6V+9FT7iqGX>aZU~q+0t4pl6AxzvaWbj))R|mv{)iz#8Me6 zmdQBrmW&t6Wqq+iCV;B$C05BKv05gJH8Mr4l?}u?*-)&Pjl>4oSZtI{#M`o|*d&{Y z&9a4fSGE*e#CtMTY?ZBL8?jBc729Pyu|u{OJHZ=hC3eXUVz=xl-j|)k9@$y!m0iR> z*;RZX)5U(-O&pLJ;-JhFhh&yGEW3*%vIoAWJ;g^dTYM~giBDv2aa87rPh}r*O!gJW zWj}F3_7^AR0C7s@iqB-8I4uW?GcsSCm4n1enJC_o&BYgTsQ40`2yeO;)a^SY0Q{^t zP{^_L2nnIvNfU6hQt8*E1^t3_q*q9P@Un90Ba%;_kl~Dz5#VQyW;$8M{K#9Z8hL|N zCX1Li*~r4lCKf?)OidlHDwx9Au5iA=a3jXPM*+)|Ffa@9QG#K`yau za)b3Jzp!DT%ol*lUqo!^Y+^?Xi9MY|9Ozt7^#w%G*N8@^6P>;eYJLWhbQ-Bc_mg_` zAgND3CSi0ZsY!Q}2>L#$MfZS$-$lC8>!dTiN@g%OGK-ZZ6PX>E%pAxRa2TdCCo+lI zlLA(r%mMddE~`Z5F%MG6Dv(cEDml*Dkdxr3e#X+sY1V<9VI9dS)}H*pa>@5>0J+KX z$ZeLd_0Y1l9IdC;N2{mR(duf^T8h>{OVEvIYDT^>fAgtq#6FEiiNU!s>bqnMUT3xA5KBjrsZ%c%j$91AR<&>P#zv z?^%<^(Z+Nz9Z$D|OL>nLVU8}#D&t$zl67X;Y#ZgzaR9*a`L(yTR_VC)|d+ zaW7t-*W&TK32(nVStDbaiXzkE9PSa>=hq_V|P*f zAbyh-q%THhjBF&^$_&|04wsW$@TFtRqYPH_#TWig_y7h4D_14?0 z_gjBzea`x_^=<3lZ6a;z+cdT5Xw$UOp4;_aH)wX@5x>uWdEZi3w*yOVZb z+g-E!!@h#OuYH((jC~{fKK4`X=h!c`UvIz7e!u;v_UG&`+uwGO4vr2L9DE!?95NkV zbC~O}%wdDWE{DSoCmp_axaRP)!yk@Tj;@ZLj{c4{9b+9EIks_3cg%4d>^R5Z*Ij;bdF-mYI=fbK^>Yn#jdpF| zn(ErcwU=wYYoY5B*Y&R7x&Bnfxr|4dYGuO9#FS}RrgfP~WeUnHF0;1GZ8v|n#%^uh zy1Dgn8{#(JZHC)sxA)yXboqcHQi&~8@ab}Pj}C8AM8HP zeTMrY_f_tj-LJUcaeq`+E9+b~p=`6VX=Ss@_Afi4?BucqWfzxSTlQGlFUnpi`(xP$ zqvFDf zYbtK3xVPe`70*??T=90r-z%|7c9qIjs$40kQdFgcO3f;zReG<|zDf@~h=+|wFlf6; z9YZzU%#@vZ=C5<*t=` zR~}S(Y~|^dA61c69II5Q;!`E0%J?cXsw}LsvdTMEc2_x4)xTl3*T65;uZv$Vzd?Ru{igfP_gn7ww%<;_Lw+axzVf@~_p{$0 zU~-qM7FjK!TJvfhtM#llx!Swc_Eh_*+UaWFRQtZ#uhpLVTlu^C2l_|)*Y|Jg-`;b$yr z^>Wp#RDZ4dyz0xVe^vcz^`ELg3N!_}1bPHk53Cg!AJ`_VbL8iuq9=@zmm zWLwDokWWL-gWD27yCXh~_`MddDs@1kuMy-Ce z3TrK`wV~FIT4!s08%ZKHd1E^KZ!gW`EBIQ z$lsz!lwVYMR7_OEs1Z?*Up0Q|EY{yLF@L_Nbd%cVyiu zbqnh*sk^T3*1C7=KB;G_=UA^oJ)e3Z_3GA3uGg|&r+U5Xji@)dUO~OZ_14yVuin0T zpVYfq@3&|YZ4>Pl?G;@;x>j_2bd%`z(V5ZxqK8FKjGh(!M)d0Fccb@2KZ!BLxWstG zgvPXp=@gS4lOHoSW@*fhn1eCLW4?^}F6K_m!&njP5L-SrEH*l}L2PPlm)ME1vtr+f zT^+kM_Ji1?v7g6YioF$kKTgCs#FdY$8W$W_CoUC^OkSD%PV(;LBgvRMjIQQZ1i!wak%Qa1HI-}{Lrbn6{Z+gF3?Pl4{ z7B@TC>`8Ok+`D<5<~hxWG@sCXR`XrWziVONqI?UV7NIRhx0u#qev9{7eBR=6OPiMN zEn{1DYuUHuo|YfCJlpb8E4Nm4TcxzhYc;ynv{v(4EorsB)eo)irIJ*e)Ox8YsjX7G zq%KX}kh(qfK=)VAB&?rr;V+cRy?x4qH!_jatEU%OWAI<(7Z*RS0>?Y?R6+&;Pey!OZ1 zpG~t$E0gAxRy{2|Eha5Ftz}xLw4P~sX`|Anrp--ToOUMddsm+4%+bMwx*ofmgr*ZEB6^Ie>}Bz0-pC9O-hE;G7((&g8#L0#K*o!NDM*X>2W}M!GfZ)}~utx7WLE?6$Mp*WIpX*k`z9BxTIbSe&sg zV_U}Uj0c&OGOJ~_%IuQaD>FZHWagC2gPF%Ozs&qD%O%Srt6ElgR!-L7tZ`YdXD!HD zk+msnSJvUId);2Jb&=A!RrQZ8T|g>!-J0xJ~#N=!QT)5W$>dRe2DFkGDAFu_ztN#BymXVAz4H6 zhfEwYcgU(C+lCw&a(>8zp<<}xQ1j4wLpuzeJM`4h>%-WvfMHFCbsE-tSl+PV!)6a# zHf+%9 zjwn0AJfhx+1|wRJNFOnJ#NrX_Mr<2#WW?DKmqy$faeu_qk=jW6kyS>W1! ze;oO6lpN(Ws>Y}$quP(k8kIk4!l+rJ7LR&o)PaBb|1mmbbl&KX#yE^=KW5>WQ)6zA ztv+_t*yUqSkG((6JT7ru>v7Y@Z6Ejjc$e`3e|h}933Nia35gTh zO~{)tX~Loj?@c%{;rhht6I)H}HgV9z&6BiA!IL5<#ZGE9DR0u$NwX)NpImeD*2$kv zzBc*s6!$4XQ&OgMo-$<0f+;(voSbrf%9E)sQ!7ucGquIk&QtTIj+y%U)J0R*Ox->8 z#MCQOe|xR(YlmL@c3SeZb<_Fupy^$w_nkg|`mE_Ir++v7!Rv0XcX@rq>lEjUwfq2OARa|-hdM-@&koK?86aCzZ|!mWjS3y&6lS$L!H z=Q;Wuzd23kWX;K&GiFY~oKv`qoh0Tkf*J57RdA;Winm2ad^mz;Bt(^DHyxsE-%{w;l^LZEN-I(|Dyod7{{`T|B z&-b5Sdw%Zxwcr-`vE$Hu`CkBNM=%BKF3Dvlp_lLq;I&Dv+)Q$L46#%3XQ4VDg?ktO z2cZWsgIi(-_b``B68=i?!L>l7sf9ACuoTcBlW2{a+OA|A(0NE?6(p8@Y;iMJ#!{C$)~0m=j2 zn?IGSbQwiVscXro%YOkt*C`zBrrIu7m4}@{{NDj2?RZxPfb)`C5`SfQkL2(W_tF-<6bD!{{o0W(oKOH zqtMjK1xK;ge+<%4r++sN1HggYOR5{=tyHMa(Y-}ai$fLv3%G|d2M`!b;%G5&Pt?x^ zeG%cyK!lV!uf|UZY0MIfp1ulr1=Qs~2x242c@elP>G5}f%_X7#0!{H={}+INO=>A% zt4RhVDM7D<)wl+o5TyGPh%Gqo_#XyVbdv#SD~!{(@vK+Dd$_g|^?)?AI4ru%Rw{t3 z%nmX%J0na)s8hS5M}Sp`R{%&rTL97&eTy&zphLQ(?y&;Y12F1bw7LYqCC^~(k+%w) z8u_YcsJvC!k=8B_kq3^8ex&Z0d#aKQ1(q^qu+ND5vv_g2jca^IsWi}V0vG;&7NF_# zPxFLEta|Sm76^j$BEtgl2s;8aOW2IgMt#30Qonh`i7tcO zRE5vMz9i0;kwT;DYK8U43m}$Zy^)T2LqTh_e?Nq6unzuT0Nx(@<^VQ|WJAIh z{7QHq*H02(Ryssn9LD*Ge+13{QTq8PQ+3cF3M$|^({cPNSchx7kmfSds<0H$^3Xjf zhDMNnHbUGEKxp}*4G6seHh_+RZa8-hVJ(0igIx+j2S9y*2|5r|0p3-1X@EvUHlVR3 zRB;3N+Tsw$0_*E+1*OaNVq8Fn_u099sLy`^@Z~5+Hp-^!N%>ySYr3D61m_#o|v2*Z)jX`EY5 zZ15c$gf&|PJ4EWRgU};G-r!ldK+~fPuaD~qxPF1e<5{-c8@z~t#7#Y$-A0m4*(fVu5cdF_ zAf5_n1mdbqP=6i_Kz^|0s?9GWRBZ|n6#1b2&|U!sSlST$0`s#rRBdGeRmYNcGD7uS zyc5+P_%>7q4ep9HqZu@N9Cto5DzKZ!K>lChVRT5#0g_K5_)Wd z)O$1Phj(Gn>(O}crD*7Xp%Gn4Rd$qQLf6gi87)_cygmVKmw~oEO?u$n*ud&*kfLFE z7}{H2iuHmC2$DwL<0WT|+@TD+r4cys?7Tgt7Ek8nGnHK~q1nPs#q+Lm8L`>Hm~ z2F(k2){ctCENMs8UaH&*P|uRKGD5VIdM~O?@LjcIw#Ij?65d5!hBikVhoKGaSqsp1 zyHLgzMXNE!CEh_RRu<3QOlqiS((6U{VF#J|3=)wx5bs#Muf-$_TE$tQEklrBD&nn0 z7-@~QU0dYEcthxWevj+w{j2wh@~|8dh<9Oy{DahbS>0pM5_sO9*8ioSsp5sCiSeuw zx+n(ED8w_INn5NcP*3ms8s#ne6^ z3z#jTXQ|K|abI$l)C5FW!kTO{+EYOD5x~GZ2Q(s&p?&KM7>D-XNh;B1MK=K7P)G1S z>Hyl3NhRlC#}rB%KvTB?beEfwx_~k?8d{Ul2s*U=@~}qXkHBxu(jB^9pLJ&{FOQ`VI2^hWO9`gaHWiNg_E4Udu1Y_ZO^3Vi3lV zVDbg%ijw-lP7->~jf^};pudeko(Rbqw8Mo2i=Y_KZAG}ywfO>&IV$fjs)z$x%uE* zb|)TM3xshv_Z{E_zMbm&3G~b5BtjG#X{mY#jA4$&JHZ^9WP#jL@se{dhS5l)Ac@5y zj~L?57l8I2TXcb|@u9sQgO97dUPNQ1?7VUa)?l6mi&O7~w(0M_aC|`?3L`qS0pw)?_F*U=Cn1;GiX3 zi|{dEBH(?1s_$sPA%H5+Kdj%s)a{?tsh6d!4G_Zegea?-4*+OeqkgKLRa=&{pK1ry zUaGFDo%aKl0p13z0jNAwJQYw55QThH`_Ba=V{WR8FbQ)}5axtv>QCZvtnw@Ym3PUr zO2U$BDty^>LfybqaDlzv2|O);B#Et9OMXJKU^1Ws!dmu9B^O`0_}lSfS(Cz#36md%=&$s)KIF_T`Y zEgP9hZCGO!kuu1e!cv73Ni+gWZg}c&CuLFV@~~Q|XxQI)5;O54-mgsW_7Bs${=@Vx z|1iDtKTPlV57RsR!}NClFunD^N$+ZTU!v6edimS>kjVJtmS$2A-zvpSUaOzd+DyhZ zX@tJevQ^7?^S^nYW&X|cU)Eogh10A03nlhOzoEqTmHo%&7559&RrS%5I+yOFFMnd? z^z@v8bZbUvUIyKik)D@D*Jfn&=}+G@@Qloy>>hMVX5W5&=(z5==^1ngPNdV^oWA*e zXb$}0k9<8)r95lAZ)I~!F&Y(C z^D-qN23B(t#W+HLZ)ss5%H?XpGq{P|4P6k#MipHA(wl?Ap26i*Bqk)w_ z9f~)ju#JJ$Oh_#=M)5oGHO?q2N=}Mr36;XbI2!~kQ5I>;DONZV&yG~8KqFNEFbu-e93_Rq9yZs%|3XnWK4H2#Ne zx7)6Tg}`>(#tf!e>q@ zZB5(IwzM5>Pt#}z+L3mmooN@^m8R2fG=pZ+EZUv+pgn0e?L~Xj9NLHWrTu7sI)LWV zJUWo((?PIR8A6BBVRSehK}S+$vpt56rQ=}1GJ#H{lV~`+=Zs>b*%&sKjbr241U8XP zf(^?QHWl8Grm^YlbvA>|WV6_8*tHb0IczSQ2Zf^r@VB#wy}{mOi(&1ulr3X#vE^(9 zTgg_j)ocx0%hs{=Yy;cK-e#NFJ8Uz1mu-Pxo~>*f+s<~tK4up@^SloW@x8E({($Xg z2iQT_%p7J%U@83(`!Mgv zqV^0w3oGJtuqFPIe+BE>^RO%ahF|30@=N?Ozrw$Rt?@P3+}_~d^B?$4`0Tk23*}sU6_QGuogDLR@e!9 z;UFC0?Z;WT2v=C|xCwVrR+NK9dj(NZR1zM-6Ba#Q!W$OuRYXEe>O4F5jgiL2t8xGugI zH(*b66TW_Kiyy@u@ss!&9)IqNU&TG~o47B27Z1ck@ksn39*Za9DXj4!#+FQS_ySrW z7sBTF4Oks7Htdd<%C}&9yh5&&tK@3AMy{3XN-kSisRxqU86iQ(Y4AW6I}=EO-a|ocGI42faRtueMeT5 z73q7@Lz?MUSZ!9PJ7g^h8$H=UcBOmaA1R9-lD%XOJpzlce)MBGSPrJ2XrF1H(W97? zi|0f2T`BgMW=k}*1f##QuYu3wEJf=*Pv>l;qmXTamq+0v3V(P}Dw$XDPU>mCnxE#c z1;DdXm=>&d%j*^Nih3p8L-*9p zus!qEE9+JCs=ANvtNZEIbbmcSudWB`L3#~6SP#)d^)NkLuc>#`JL#SEE_zp3s&&&d z(080PFAZx|*pmAY2Cq(`@Szr^MG+fiKS*q0!QF(|!ya%oafU_vTH*q$_VvUSHi8>T z8SQ{}fVdg{pWI=I`!lHwYuqQqSI3Wp>Q?Xw6$UR*9nce>)tI?b9sTB8c8OhPSJ-## zD!az6!>aFl_5-`gZn4|!M|Ow(#C~SKz|!wmc8~qW?z7+71NM+TVt=s5>hA4VMzFZ(9sL6xHaqoZMhw{hffGcc!hB0F5H!u;cncWm*wSndH9K_$SZLV?#a#E zi+iKjR^e6QIl>oSG^%lb9srxdK=_fU!GqybBb0~naQHH-1@9VByf&|co?VYe^B5k> z<6wDMpC|A{p2U-R3U9z0@S4L^0)aW{tn*^E6OeWJ-(H1gO9cy zd?#!wck}n*VPr4g$3MXMJ-`q0L$I?v!aw96@sIf@@XGKh{4yNpCwNUQRco!afz@O? zt-Y3}b+80Ds*eznri>CzGeEq=v{xOZ^gc%Z@t*F8lpG7 zyk~X%ujyOnQoZZHt8c+SA--_rAN3@7i^NKw!L!~z`4JZ8r{#GZm&{@6s`8UbarHA} zCNKFaXhpY;^QYyt7tY@|&VOvAfBtoFMV=;r&^{xaS)s41?*n}EJ^Mb`69Mlv846T7 zz*st&S)l~Ql#AiHjhw^zVv2>p|A*pGj71vdZD`NGT%i;`5H0yr5?yrPl9vx^sP0f- zEJcl|G?k4~|12LOuC7%yt`}ePHm*I=xRI7>@M&qpRDHA}_&x+XR+XisZGyoge17C@ ztXfL0xnjNY{F(;3thja2_Rr2U;z%TXD}Ey`%9(N&&P#Y~TnrBgb!2p@RCsUVTM6He z2;m)})D_$h`|de%EVlIT{`l$I5YXJp9K@ga?_)@F6o*z9y&1>GE}W@1eM(7uGUkEWRU3JV&^|cZ9nv zr=AJ#5mjVW>4Q~BH5vZwX{vXrHl)UCqPT_N7ZLA%2RNfK@R*_c!8Wp=)nh58?y6$k zRnnqvph8t2Dehk?KlOg7=7pAZ!xakad>k8C`CX#)S9NS)AN9Z;$}gZ=!wkn7 z=C+DqL@|Tc#-NS)O{vuL5LbQygN+hb#IqcUwcdp=tV=or2kV`HJ#_d2)y+s-k^*ZH zr08a(NHB<3n=;eXUB~RBL zs+wpIfG=r}jQUva(J-gL_sKou3M8&*3sqCR8B6RZ#I6>XL%WIi@7fQ>8I|gWIyTZ@ zGh!uqerKe+418U?0<7|Mz>}}y_eOl6k z8Er_?Fn{3PqEEwbF5Zml!|Jb|t^BB|u;h$}6dFo>3q4+~OR2FgrN+9H8tYPOtV=1< zske&tDb~hnhJ8Nf+dy`9f&TbES|0}8x$_v;1fG>Jj*JpAqlETG37w4+Rx(PcJQsH$ zW@8i!$UX*P##-{y;`q38c(fYJhh_nR1e&#+7sgkEgfOBih2^$igdIPd?>4LMR6K6bE#((e<7at zm0Nhi9eCz3dJKH?Jl39QPmvB(66#)jFXkoIhWc5jSjSt}wT`e3w)VHKV(npF*4o+H z7CtbD)g!BWR(Gtvx4L3=!RnmVDXXJahpqNm?X=orwb5#|)iSF^@R%{fYKql3s}WX% ztomE^vdXmTY}L-HrB!3AB&%4f+E(FKK~{cN-c}W@+^ig}tgVFUsp)~~uIaYvy6KYX zYtvcN3Dd`>gQh+3%CXtB-n7!R#I(RvV47~4WEx`{W*TVfYwBt0X6k5aV`^?{Xi6|e z!;?p-sk+I>WHyyIxti=vCKJ;i>-Y7a_3!m7`UU-*eo8;8AJ+HjJM}I4M)(9-rZ3Xx z=ri;w`Z#@rK1lDc_tG;#i?!2R>W%dzJyx#`3M~lZ$s1$KO?SlFR%lPP2ijfjHpbp1 z_(VDj4=x{T`{8?JE4+oRg(s~ywR!MH@*4cNjDjbZT=;(Jj`ejK*4|Cw`6V76U?Sk1 z%O5^WJh1+8)@v{ZQKXn<0g0_7vTr_v^XX{ z6#K<)u~lpmYsGT-Rh|c5Os~Pe(kL+mo|t-p_U{btN=tBG62O@W23N;bXy6y!1h44? z=!-3&MOK4OSp+I(1}L0ypne9u^ju%iPu)OUwZTfcAx}V0^+iwnJ3NuG7M@5Kd{MWN zt_Ei$-QbLjH8>-a49>_rgEON1q8%fj8YB3a!5=vezi3wQv!%EsU*LOO4?eaOujDI( zS900lm0U+ndy(H1UxVBiW5gKpJA6s6CJ)eRCh`#L4;%6bya^Zb2UZ0Y$WyEgJV=pR z6<~?7Po9UfZZFM?nn9yirCy36r`~GBQXhl<@x=^QQ&CZ%@f%=-w$i!*w}cl#2HpX_ zqTnIm`wL$-{&1G)3oZkEoXd}}LS^zpiSL6vA~EZu4`9Ames6#e%1gipLFJ`yXx zg*X}^=U{DkQJ{9>yto7WwZNAVD?HR4J|#~8f59PpXXl`0$JpomQ{c1wDDWA`7#Taw zKL-8`x^|46;vWK^ue{7zj(HTHwE6#n*eX)je)mv^cD6VZ-|-rJ06Po7G4?nU2X>6%sqkM zfz}{nn>f}e>}_ZsFt(9ng~B#KmP8*e+Qn+uIzsgxI$ZT0I!yH*I#l%@Iz;szI#~4{I!N^%ny-2f9jJN_%~QRH=E93U16Oqi zaDToXxF6pJ+?Q_!?!(^$&f!~tdvmo@KriSBGMdfb0q)5+0r%i<19#^efwTAq;7qBjh1n$IF0C$8Q8lxTfTfk|mH`Dg2H`8{iACm*L zt?I|Ljq1m=wd%(-RrO=qO7%RlL-l6bl23rn3q02%-hy`qZq7RaH{%_FoAM68O?Vn` zW8NOP5yu*ZHiT9w_DSGvfK!Z=$-FhK45VCfWi3#1_uxroIqf31cAXB1g^;=fWavQ4&|}H;2Z)6^8nx) z+#fgyx-yIgazEhe+!r{2`vCj%s=(EF6<|L#4#7jm2&6u01k$Q%1kx&M1k%cC1X6D` z0;!i8fz+%ZC>>byOpeIvChqjYDdu#v!#;3fkP;3&BvzB3VQu#d)_%WYZTOqKFMpR0u-1GoP4Ih#q^z`*K}i>$Nz6bu zo%uhc;v0e{`&XG+7sywuVxCcZ23e5=k_fuI8MNOV;g7x-I9@p<5AwlrWEHfA){w0v ziEJnDYU z<_~|n)mdG#mqoMs@X`za%aF#xc@X)L4F^x|jvD7}D4phBP)3 zlu>hh_mniYz9EfmW=Lb38`9YRpterXd_x*L0bX(YKqjlCu~Q5=>~zRsN7L60IqWP$ z4m;P7!_HGX=Fs`z!7io?44LdgLngb(kjXAHWU|W*nd~YBi9Nt zSzGByH-d&NL*E5o)E$!9a+p%{ zO>e>vwhz6fB%t)Rl7P}*l?0UDgVd=3y$^c28GWqep!BJdgVG`;2L+P}a!`Ei4rm7% zQ!-*E6iSNcFBrwHDs4J{kuOUscTo@?3=KiEPT+BW24>it~b zh8$HIH=u0)MdyZ8gI3f7Xx@aE=-$8z>zVcqG_a$|F=%ujr?KP&jU$g}JURJX69<+K zDbV_aHV$ktUeL$ECp*LLw9?oiSLrzN$1|;+$?zXO6r^H@=iEgU|8hBnqCvSHOe%D)=FF~ z^Uf3O8BoNGg&N~bVDB}J>F}Lyg?Y#Z-qY=vJ#%1=%!xTO7v{>!FgNB7kG6swJW z$m)WJ0&RE}%i>r(tIrZxBIeg@mc)`-3Mk=*OxZ2>#thh$HDk?L3)Yh4uvXA$PlZ4C zHn2D9%i3Wllx48UNyNPS6wASWERA(w9pMrGChLTiXJ^)hbtNgJKTBubSO&DwGg%h+ zH9c5Q*cs)qUYJ)0fOFG_^=187e>Q;SvOG4B<&y^3S>+)c#0JA-`cO6uGjAh!C$?yN zVULq9pj|%}mTTk4;9|Wml3!Z$3$`57+3TcfvGx}%2xgbo{{pwEIa@$l{Hqoi83K9A zP_`W2u2($Q27}*gLmv$O;$Nu|27lPQ;6>ih3M21gUz|Jm3bp>Tei%F@9ab7*@CyGC zR^A_%XoJ7P{7F*`{NPbaTa1i>rQTRr4vvODW@rMbmDtO4I!JqxR-)HII*^X= zQGJ{2F0J1|I)P@m@n4|h0UGZAM$3cjCkOspG(Ad5;{Wq{9w+~wzTn@`7wiTJex_yD zfu1589O2%^E(LupyAR}wJnTS_4<7Mg@WzLVVOV#Kz^ZE$_A3}8#)@&+nP39;C76UY z*c7#Yp_qof4PM6%1v8EP4GPpw1=#ao9(H~!(HIozc2Q{SNcBj0eJWa)OrrP z2ps`O|0AsGJ`qR7r{M1&7bnC?aY}p!ZvPo^7OT5+;tTNpAt@(|$rA8Z4QajjR{U9C z@&+^}fA~MtmsB!)9h`G3X>IJtU}e^Rq$N5;RUVI zrI6{oC71twtx_dJl)L5oN{R@c{|Ar(9Dq#V5M%^LAT#*rrBvH#aOuy2i+>JW{x2Z~ z_!?4z3-TLzQGP2g!JGaS`JKEfuYq@e1HAno!27=i*}#vG9sKk%iVsq%KjdTiL_USQ zfIyzWAa4+wRJ+J%Ce2E-)@&fpvV&a1L37lcAl-7&T(vTqo92$yeK})qoQhcCdth%I zv(j?J&N)?-o}-~}SBorn~u^b}db#mez#Jg50hL_HD@4dSNG> z9PHoF7rQd_*9JhQmuKwOFbMlL4AF){K6MW}Hw;&L_{NS6qqNc3GsnTOJlX(B@CwkJ z%VFs@89E&r5)YZ;Y^{JCCPzTMZUgnYgRCR#$v#+#Nn(xNALd{;pLy8zX90E&T7=yY-qaRj ze~6{nA>u7aC0Ag#qE(=k)__u42Rdm3bVWIKlGud(BtVsu3ECFu!0OOu-AYzNqi>tG zUE87U#GVqnv7f{q>@2ZQ`+!V@q!V-%nTP#?4r@n1WB){cCOe6l%!GZFC#=inkV4Sc z>0~8|(>{cC+MC)(+Q*QTFC?q9Psjr3!+fY6#r{OcwByF!7pFj>oz~8PAMiPLm-zyF z%6vt3L9Tip`^n^%0cD8ZD-Zn4M@g=WAXZtd&CclKe;002jd`rHD-pV)RE8SbK z{I|4!BlKE&q#gxHa~-{|UQdtKV<2~q)8qB}dV-z^DRi=)Qmm5+nRFAqsoqR)uD5`M zx)rF`*4W>oE#%eh^)$W1i(C=Nva|H=dJny)+L=c0jeRis=zaBmkaG{vbM-uZpcdCH zH)~Lqp5He+Brz_rIE;_5gu#|D)DniPFgT=sLh*5&ag6)wTaGRF#YI|<xMpoZ}#uGy7hieHL>BtB{ZohQnY)E3TrCh;Ay-@C&TB1>1Ezw9^ zT*?qjs(4H7>sxA3-%{H8mfF`Zu6<~5xOL*69v_kzZ>ey6NrfZyL`%ht(uY{G3<=j0 ziz{0E=y>DN!6EUrimQ>Bs3jXEvq^ql+2Tu~q1MSSJT=rPU~oucxJc=io?Dz{sHF%Y zi8d*J&Lbo-G@LbPs5N-@#_@+9A4a!LqqFpEsGnbWvPa#X{kksrPMV`x zp&^!Lim+57B2jDo?A-*1B!n5SE)-+K7%Pb-VT5e`yse9SS4d)#r67qVZEwjmG10E| zzqU2TUF*_qT`Nv+UEGajYc)ACxvV6jU>gVlPqr|%+h2@ky@IOy>;3P<2WkRv4L@=OT!>#aS2;z zEiOpeQrg5B=Phxg(TupQ(Nb}Vr4HgFN*@H5J_s#+5dQy>_a$(0RaM@vs$Nyn`)Z6B zLI@ZG39-Anx;u>l$1 z6IMe+U_wL)F(QNz12U=afBxrgue!R^fjHy$`(FL(zH`q#_uO;0bI)Dgd(Iv*|Fu+R-4#s0f2_-W_1wA@m z5Ule<2mMu(S+|5SmhfI}#uW94kQg~aPdhz1rLNKeh^Q(ZI1J2I?&7s}w`%Fscb6^5 zDPcWL4a;t7SWi=Z&Y^$Zg~f95gX4uiEK}vegcj*NUr~?m8^FYT#7UMW>e*^iJpRzm7d(E91i88MVOo6aG*1S>Cnl5Djh!qqJp+` z%}T*HHW^MyHWXuOKtFYg^MJ&(u^EBrpK;+W7!Byj&E%~@Mg~GvIy?lF9lp)WK;c4K zyE{UK)1eiSJ4)Fc6=+2rN?b%4Pw1&Xs%&+e7F~N(nd&yJtm>%J8tv91spNR%l4|Yl z_BAadQy3Cvr8~?@w`Qd=F{rJ!73fg!U!r}?z0-M zgB#)cV1y&0)(vxp))ztUbBg+$FZB9g4AkhKD^r{kL$%kVO{_{sNJi4O*W>j~OTNuF z2Nb9g{}@eQ2Tucg-BRaA(|~K2=@+w|p@AW+M+as#wqdll8b1vX_-R+7|CsT7vavHK z+u06{bnxlYc#~;2p^F%{*EiS7UZ-zz zbDcqj>5LJig&BdM9l3eTUv6GN)oQ4wnCwn9Vox>VO*Pq_s@cuAv{%k^>6sESIhxOu8lC&iF0)E}A}2-#xhnl(1@n&!r@VF`%t-K}qU)3W`|m z@j^eXF`~5gR4q*Mz);biTjc5)^d+^1^w!B>|fPO-bloJM-zx%yTmyJ2mLaEp>%} z8IP&$X{IpL$_C!Bn5M$ywM}8hW6HP9wi45druJl)dMkXI5xLv6p=pL{nrUm@tu;&g zTH$D~OT*^VZeq9Fik?!nw9kyka4u+D&*!J*@U9aq3*?ps1=DVF(Q2e_x2?pc(33kQ z=w=Q&iCAX7vx4)T z6|QY#Jn71vrtR)DUkDYaSxufhjblL789q?k@kCSEJ?*(ONTTQ-Qx|Qfz(tF3+!LykYe&Ub1S%v*L6y3w4)r%X7dH#b|={A^Mgm_-<<_{nNy=qNHpZ6o7p z4alabIbcG()gyYIWc9Os27`E$JtNAT8k1ucJTs^h(5@lEC$EiS+DOv+n^GeHd zN}@JQrI~Roh~FAkXRDFI3~K?|6yF47fMZB*zTOt|R;2@gfvF$}OU`8|pr=B{1Bb>S z;8RD73>#CMieI&us*zEKEY*6-DTfMCMGUA?edPGIw08Ki70u*AFnakEVP*SV!DGz! z)0C$w2g3D%h9IKWsR={t3qN;;qR#N0w?5Dz8vTq!#cJCyvj+9_Xhl@1hG8TV;Kj%A z>~jfKX)5JRO<$FdfqiY%d2JMM?NUaWna3|2GJg>RhRoM$jiHQsE37I4WE4?vCF)hI zN(3t?O2~p}=N1HO3nEhlRr&PRCRC3h62)Ud)Q$x`Z&5iGi;5+v>ZZ3kP&SrCB&o29 z#v-jus$_aAor(dd0|sIZHR`3eCR8q9W>iaWRul_jSha!Y;o*;aCBXU&W%xEY{YZDzV<`K!x?zgbK_umm;XJ%F7~y zdS!}EZ5aYlS{7uAPGMONwnS7}5i16{i6%BVz<9nmnCJIaEU?YGLQQqxY6EK>UMVIq z*a6mfo81J!3b*AUmOHSqRM`-*;5~`YR;wgm8<_H*h>_6;d#j)-7tcH86oD($VgS@s zYCAC0N?*Gu9Fa`6+03dJd7WblDKA1)yGa5(+7x8511TDvW|6krp=KpKyXXXoU}g^l zUC*h|IRucg5Y+~A2*I5w)GW=^FTycE-HPe8Ruo3CD4Qq3~AF%dCfc+Rr; z4UtMJSz5$U&Uj25NMoa8!6y*Gb}$e~ByBm}!oNN-2Qpz0k7Q;h_qF4GI~eMubYmK8duB9yH1c3-Tm zRxI>uTNJP<0=Xs4_!Qxyvn@a>Sck1AnR78bOa&H)q=Gq^8y0GV`IzB?qOLMJQ%aZ@dCd#R}J8bp~h1WfBLlc154I zE7LlJRIey$glIZ~uUL_6ZrvilP)_>*m?q?N+v9V~24M)is8r(hiHV@$13@%%IvsS} zJGBO#rW(q5A%ZJ(YUl}Brl-OmpC-Z98tko2*n+*)fh7nko4@#UiFt;^w1!}BZNd!f ztqzPplXo!z4JZa6;#z*1^&VScYWi7HiVCx@x565IfNV>v2@O6IL(DzcF>q+=)jI3W zcni`I&Vq_;WGUVo080c)n75+ zL1GCoPOY;hjW<#9O-Z}brDBXr`Wq7#rXw+Crt=$HlYXrBn&j&P!;wL?&U&PGco`GY z&?S0)Q_`3uiBvJF&}F|NG%FniN5 zRcbprT4O*uKV)#y)jCs>!4+S!B2~z+Ph7Pb`{}8527qH{tN`y~2&rqGF%m|0S*SS8 zmA*4eNZ6FLO^9m<4HV_>tQ2}H&T!nEsiNGH_KI=~jTU0GylA-uewr`z*E$;}40e^p zPQ!()u%*dbmijBiu2DwLxG~;D=W9%uIYCmib?BFM`jT#Qn{Uka_dNmpp)x_wIl z+m@v3((`BqJ(mS~?o%M#;C0&*cs=J-&`m-3c{B`n-6kuT9j3xWJ&jh-<6z*!O&HMW zo=riIVZzTH4&3!7V<{!yp`L~=XHOjkT>$?bzh>8H*)5Ue0eLM zlzSC+FmCRgY% z{5uW4)9^Q&sD&=;H|cbld~}(3T}FO$B(I={ZCeVH4L?1s+tO~!M-S;D+{B+ewc;pj zKF(gWy!VVc1IcFOiSv5TP)Vm4PE(8|dQ7;bpr>DPS5ED=q|NEpf*!1GDd>S(+)c~V zooM(?iJ5KLf^O3SZ%S5AZ?+Wl)F$pGeRFEFpr<;4H#wPRNZ)TcD+J!7 z+ic2hT39;MLfN`Q2Ie%9#xbXc)DxUEl13S%V~)&_49zSEk>wYNqa9g~HMbOWvmbZU zuyyOUrJx)1Erl*iZyJ1?QGMp9a=~oi7tA^Cf;r((Xfqw6&8XOR(~3#+>L_Ka}@9!`3hD(%ipA5 zF!B|Q>Ne+D3g$p{q1~2)DVKtg*Brnum}3crc9V`dmQXOqw+rS_LqU&zw-n3?fs9}i6$r*!$(gtA>72DJgs7d8v+be zG6Wb-=B!k~Jh)JpVk9vqhT83*JH<$(hmoMtjb!>v1^lK2^_dI!O^Ho4H89oGh@Qzp ze={|24viP|I1BKmMD(}|{3b_wKm~r2ZnufA2Uwu$O-Xc{_~vX+L62J?+{oW;;_Gn? z^g2@xJtn?6{7~pI<A=Zzs(FCj=p3lYAdDhNyf){)r7p53?}_0 z^5UdvGQ32-!qk%R68Q?<$#59MOy47>pfu{b8n5ad?k+RJg-EBrwwy8;*Z`H{G?vsa}Id9q02}@6zKLpRO z@xJ^t`YV<%#Nj}}44uDp#WDpJovJjaEIM5f6{q0YyCsC?En2W}If<4mGK|Q1`TU$P z#!dJxpO0M}VY__(5OS5fFh#iVM7Xj9pd3L!hRf$yC_fR190edx0F{QT$fSG;k+aZ| zx6m2x0vi58s3`!FQp1sYj?=hP+u~090(WXn+^O|(r}o61hB)rj!nku3z`aXlmXDoL zW&$F!07hm3Rc3`mX3~kwq*Iv*s>~#$%!F7j@{u#9K|seReSR6WjG}}Z@%)bqjwqGyE4HpG{!$|-W zzri3p3HxfxvAG+9!u`qGMdrz~G2 z56Se_S=fX%d}+nf<#_B2smwt^D+Mq(AzA9D6xelU41}u)*n}j)gx1hwgX&$jOmKrk zBuIQNPw6E|kVnd|5<$9JFRuv8;_uXnb-q)rDm)Kn+OaOBz^~R`i!N2bukE@8=d!0B zp*1+Ay{3054&I)G2EKguvNPhjID{K_uUNL!gy+rOsFcam;>C-W%{E9{kvOhFD-zG* zbmUH+cMABJV56SYNClZmUe_}~&glg1iZ@4tv3;XyqW=w@chbqr&j3dXi(^j0U;a#0Lu?auR*q|m>5GB=aFkOm~nlYFT#mrf{e4#=sPM*7@+OTFb z#_>RTotih z7jp9LMsKz2EC7C5ui)64J897pIjnukyxyft=4v)t+bX2FpEMh-*kC$_;1HKcBtvh$ z)js<&`y}+8Q*a#Ew4|22SxU;=Clc&~*7hX7Su)BS&6+p!jl5ZU%A5H_-pnWRW}eZ8 z$I4~8k30S?0o)qEY+4BKG`LQ{g$i!s^e#PVj={DN%L4-ewl#<+u;n$y#F{z*AJLT3 zKp2}9?o(};#5G~MRMplp*@R6-SZf8hdxF&3fSuUnu*a85BjHwpg5iV(UsnnO+fop? zwPxYcr6Zgb`>UV54*Hmi+oW=*ioObpx1 z8dsZH6>T$15p8CPq0KBQw3#J^wzi41&LAq=OMOslrHIy2M8P#NWT`U@Wla)M>#8#h zXPs)eE6OmOwOfivBsYbjM)rK@nHt8M>LfFy9Sp5?RT&bqmWded!hKbjQYOSvk10LY zd`QmfFGXl^HYOSZX^f;jfd@y0Y;SHsRfZldca4Kdi;y*GB^D+up|&EfLUElF7AcgU zIAM{kY)-vqP4Av*5IT1hM5C`gu~dli6=qQ;nodCjK01dC5IPAZ7gxZgiR(m9Xoe+= z#->(lnyAU0@#d4>4&$x&>1bgXa$7d)Me%B=oQ7cm>uDK)_Jkfl%m{lMG;wN|38!E? zaJd#I>STEp_qe5t_MTBScOFh(EtxxSS??Jzv@jb{JnN!92qa=yDMDE{glHyaR7;1b zkN~!-Metn#zRkcmPvYnWw)6s9dV$+aO_INgs`(W}ODUqO6wwwUm}V&=!Go!nQYepj z#!-hI)_Cf?Wy=@!&W2%zQY2wb0#zsFGjsN$#U`}U`>9b2lU2JHCU-{sVoN2y^mUAW zCp~JG@1#e~`ki!sCrQ30t>0-$N0}4nEn5D)cq6W{6FBN{zq&mp6u zy@re#GA8;6zS21kUrX8#UzFNs$V_~DXuK;aDa9l3-qjS|Kw2GT_n#9D$G;IU(kU_r z#ghns2{c*rb|Ji32ppox_{w#ee=5Rma-v-N^lg8WM)6>9xH@qYCX}ojv zR@B^O__Fard@1=5d~JCwzSmrf?>xVTw+}slH!xj~(mW?x7A?g4Ao1pV`wcIAGwt)j zHT(H=O_aj74*+N8SYjPB0!lfu^$5c9|-Hav5K zgdcgF^~d-=N0gp7&VoxM3>eEQ|AHsX4 zeAR|h#*GYzSN(Vwr=xb6P}g(dyhgYP zq*oVez6-UhI1`Fi(sh@^-R_`UEF^cmx^C>cZWln;{2g&Am!m6PemykU`jC-=S69C= zx-LeEpD#pR=iq-9A;F#MAPx0&0-Y?Oa&?{1HFuXn$L$h+N2^Sb&qGJRcUV_X*FjzT z4-zVrp(}@*0J^bVqq|0Q4Rh(%JE+n@EJ^WciT4Zn()BUQ;o>CciQ6Y`o49%6rYPDe z=;fV`F>o6F@)Kz%ZniQ9(930TC3HKLqk-icL?|U)=cIaeK?RLa$r(@jeVhjx3Wa%c}+Io;;Y=jecr&SfT-i(M$8&iS2R zH076oI*$>${z50ROi3}bbB1#4+||$_yOQ&a7doe?oC#=>;*ir0{98Kr?cBR_=77*4 zWkSp0MuKj5XJcn|XU?TzfvDph3zoCtI<}MvbtN9eD1*~BF7cJF@wjuUK*9YAv(-Ekd9 zE8HT&H@0oAMh<(-Dsxfp|m zp556Pc7BU-dW?-*4r+hehPJP6f1v%|_B-2eYhUefH@DxQaQn*ks|N*LF-W?B`Mn=5 z?EV;wZhLUPxu{qXifcL6l* z?}$se93ANLJFyIucesI}@jIq#M;~e*C3Nk(VZ6MNkl>mfG^z|#D>%72bL~U_Hc)$d zS8y)Ce%C92wuZRE>xGSlmpTrLqW+=63w=Y&;?|Px$-*Or`)nGzBP+hbJr<`WF21sm zE8QTp46blTl3(H0!cFZ<3fCj0oq!70?re;K)99Zc^UJ=UgbLTXe3S`QZLu>s^70j~ zvU&565A7ckc?y?XsR~^EAg&yAVHvt|xN{xd*}DWfy$lzRBUD(1d@L5aK|u4%(0SY! zB@}#)!IQNw?*eH2PIKA$81Gq9)u4G{eOhEOf|D$C%<`3=BU&>M-XA|dcB@ovui1RGZE(=~0ioy0gqFjtNz%1F9z|^r32qS3sxovQcb9T*yB)8W zIhvI34;sJITy{RjE|3%@cZ=qHCm>p?ETh3f*SRzX8N=If14C`sIL=qLU8Zo`#THlY z-gaf%`G&3x*{*P}?~YyX`E?qwUnTliN;cn``~{-gcZoZD@mSM_Alo?rn!z zDuLR1+75!dzs1SDtzdm^6C5M%&o@b_>hhOG+PWi;!%2!? zxfJrPDM=T|;?KVvt}(;YEV7@YmxannWm6N%QVH_wG`_lpDfwp=>NqIGm%{m{?P=n2 zkf*lerpLTLD8E|b=O4iTUP5=~Z<~0g%%hT**KqADwfxP9af78%F5>f$4IAj5Uui=F z=xU3Td;SXR%U|lCi}GJrSoc!%U&)_YxXOkOe9tep;rS&31vz(RU@7!on&13F8}1>C zDSyw;$sgOc-iGC8g;4(R{Gs`2d1@bv5AR++!TQqm?aD*>18vNp4Nc7F6`OaEr9xjd zR-MOKD4%EtKHYMVjbrb=E-gmyzSri*Cqt04G5C)XY+oo@289kAH@};O?A^z;nB?8( zS8FvHYIo-S{#zLC_i_A+e_tp+Cf{uHF%X1&)>`g9AMNWb&O<(35BYRu&q3o@sGI~C z-^y_IIRC_ho&nDf&8Mv-=AO#DlztgcFrAnBD4qzL18gcKBU>shqi!lKQl!!i@cq2ssnlA*q$-3%bU|t^;4yT6MasWQtmwY<8Nh!KPmHBfUncz|hWUMK`_F{i=>D1X zcc(uIe4d!r)ZyT^N-*gPhTcOw+9+toq&^3CTC^4JMq+Y88Qm>#^smJH6Wv3^O`j*B znPUacQ0C|-OzWeh`6#(vNtlwSe#}_kkmsaQKTaJ7_saC%a9<>T9x2Zv{9D4J(Nd`o z7g2iYeDTZT;t9zVN?%gn8_%hwqRsL&A6(Exn*|?jPD$;2mQVS8R`99Ml70yJ3?a=E zq<>3caZ^W<{z$}6rS@d3KM=ns-BomFk;9=1(;Z35k%Wg4W}8VpBbfBZDB+(e;h!Zm z(i-`5su`)&lfIsD4`k{G5+k}UmD)!4Xu8J}FXKQe{Q=>bIzIJbxW_WqcNqF%Ql8Jy zC8+}uT3~2_p{G)kZ=@ywe_pB!?iAvuNUZ20fuo<3!?DC4%cmnxPW@6+Q9R)z8G0l! zHFOW7`*FdiV} zL3g#d>0_kFrJj|(mOhO=?!Or-Hc2WKGwwIkC1$I*(FTzUuH^Sm#9S}EKH5N<6npUx z1e0c+M)#5aKGNSunk;Fqp*CnChWY_dm4avLPGY7r-Kh%GW!pnv71?g1TSs>WDGyLs z-1LX36W(UJZ!_KP3?0UF|AW%LO-utZw-WOvDc>ai4DuO8xJ&S;Z%OwdYK$&wjIWZW znW;AmjOTlWriwf-VtMW(Zgd{&F-QD)#P3a4MkvvN!XJ-9lTu1mEGNWb=uBxv(KhDj z*P;WX=NbA1mR<*GCXho>VG|2=l)gq%O#PmkQ?3o%SeA2!cwVY zNO>^v$1rXq;Y*1BH-+iWB%hgt4sGIUP(QCIb%If8rD(jL~{G3&_q|IPC-t8 zojM!td{WLQ<$TiIPMYtK=9i>d!O#^F8c{ppd2_;~znr0$Q^V+(@~<2PF4s`Pmyu>4 zVonw~JzHVA=Te4qN%I}SL=P|*7Z84o?ekp14-vkMvCb4X%_r3FTwc?rYHvINXwWlVhMyIBwK9%|yM(NL_x>84`u1!tC zGvYrR~)9{+rYvQ@=?4IUS>)=F(lMb?J|%r=*+H-RTq3Bhri0XTta8^yTT# z;0fZNr%z1(BE2?!Zu;5ubLkt>FJwlif124lQ%G;lbYwn}$z&#Hre_DYxg8;QWbVqGmANO5GGC3;aVB$boQ>-;tKx?E zLz&0o568P_*5Wzkv6^GWm*NTW?940iT>L&9&yN?zBjOX|Gvhttv*ItuAB(>l zpA)yn=f?jOx5ek-*B)OGUl@197scO*C&u55FNyb$FUN0Ed_r~|dUx*)$AC2e4zrk-_ycWOt@pJLI zctN~Ael=bk|1thkygYs@n~G1%#@TFqZgxnvBK~@|Iy)-9AUis{SA2E$BiS+WHQBM* zvGKLpeY4~6{Plj>3GolJE!o!iN7=S)Tm0kf$Fm=gZ^#~yJs|!`_7mAp#5ZOqXS?H{ zW)IGOGQK7IsqClYf6sm@nFn@jcnO+4=E<*%Pu$<43Z~ zv!}*SX3xx?8UH5xmFzk3n(RMi|2h6`_LA(S@w)6~*~{YJXD`oQ5xM2%L1H+U)*%D;7V%f!lv_^ z&cWaQ&T^2&oZ571)5-Xqko+}Y*fclsJFaOkzoS8Q1pbFL^|;?bO?`j+H`!mIX+qQ3 zrqTS5;C*bv-L)Nnog*EU?$a5+ORp(`{B-O+F%!ma}L+=jCeL*T0#PH$M& zuo$@c4PR_HhVFdcXEw}eIHX}p!=#3paA!7j@ZZv~Z$k@zd($1+Fub9Wzu^tl4dTnu zRj84nQT;m&BkQ*`G}gaU|62Wq`t|kC)vtm3c>P25tLpEnzn#!6^*6$IUHvunSJq#K z`^EL=*Pl~=7XPR6eop<#@SR+LLj7EX9Y>0zfjI)uVfdX>-&223{r(=2fzh~(_UAMaKfx3I^Rx@vn z4b^pbn!H90jdi!x-CTD=-AdfAuDhb{(z=W4zFzm0x-0)=urH=Qa7#cz`BWb`MU9SV~jhhZnwJTy4t#-xbIe%u6?(5tNFc-`|GtEYhMEH zh1#tQTMOKiwU5+30^io!`)cp0y`%Ql+MD>hwf6ejYvH@9_Hz6$!SAZt3-LR*_H6vl ztv$VV8Sdg=jQjlBF9JTh_L$n4wKHlD!GA{Wl-f!7+h0d*3w~GC?n_R>O}Oq|JF<2- zevSNB*XH1lYTl{YA|W-eFMDV zxuxdDn(Jz=skt4l{a;ygWzA*yUs-c8-ScbCsX2?kb81el5#Pyl6*`NdC)CWXISw>) zYmTltqUNxg9=He9>|ax;nSj4QV{1kO8c{Q>rXIf$HI+4S_4ev*{BP!cd-W#xHdVh| zy$)f|lHzG#R@YQkKY-u%>U*p2tiG-K=IR^FZzb+4tFNxU0=P>7OV~xYf4%xE_}x~0 z18S?JrmD}xZ+Z0+{FYZQte!*PvDHgz=U2}HyrlZ@>O-ri;dfy5#Ogd;`HMRqzcF-2 ziOUigOsSl&AQG_6_)RZmtu zVy%Iy`|yKpP<2PutyMP(P1W^P*H&HCw7%-{s!OUa1pZv12i4hCr-KeuRm<>O47&MM zU&Qb7swaWJ2Wh-ebxiZts+sti6c8IfjE28%M3GL(FeV z)g=5nnog~n!jTZ8o{VG+l@NqgwN&j}wRhFXs^L|QRn=9w`s=7$UubwzMuDoR@}0^p zm9I6PS-GKdJ$}zsu7Ue_Of(1`H&=yn=c)(#&b=Mlu*Au>c4a5bWa z_%G9)M7Na^P8B%%XReW4h&MB5*-=sUA?)St^SJ0i3z8f44Cw>gw9=aRTvd(uZ z;d$alMY`X~$Q#uDmH0#GZlwDex*w%`4c#BpeUa`L=}w~iSGs4>T~2pK`ah7?jLh!h z8qA}FpP`G@&2)A8|Ih^)+_YU=Jaa19iaal-byB}#QmJmq=%65KLsECC*da~ z#pnr15iV9nqbJDc3DP_vae!XExf@Q*aAJm$ z=P>ddMxMh+GmJFDNb?Nw&k+9%@y`%{B&9l%c|4MQjwGKWNq;2ik0kw(LLdEE@NlIh z|4a_`nSCH(Jt^xm2N9N%uV?6i#2-iw2NLr|y4&a;P5jY>k7w$~Gxg)i=XgqWJS91v zrSuF-=`Sp$8B8XpTO9G5z31s-RRp2OX|@@ zY!4SPrx%g(BJ#P2e2yiblMO5_yH>_+WsX`I_tSI_CjRpVmeh|X=4hsSbb2i@l6ntmZYGDD36Eo4k7MfN znEE)TK8~qh%hZ3()W@*}j$;cPNB-l;XB_#grXG8i+V5Gyar%cyF~%G4#1(BEQwz-? zek--sR)%h6=vE1hzM8>4RrGbd$q=x(>5+`pLbpJ7JY^oA7R@|9y_K${HJ&`jXDR^y zfNz#umHq?qBExt|1@8FCd{5={OiX&= zGo5^ z-_N{HAkPWR`vl>T`5`gm7;Bt?CGX?N|4~vtN_cnH{_ad`cc!&FQ`w!V{0me0H>R>X zTj}m>rMr{o?&PyO`Fx7K`#f>eI@X+z_iCn6=V!i0_ow2LW^cOO6-h4?IQl)^{}TG> zdWA`I3^^RbH@!Z=H@$A=n_iER(6qccGnKl8{4Wuj^iZKm??akPGAkK+rMRR#jP3!9 zJD%`G#`5wUh?l{IsA_0@K^HhW$3wLtEB#s?v=!x zDQ-GLcmZ>_fN}qeaf^(*faS9wBjvLIl<8BPX%;3b|k(O{Ui1wPK~a^Uc|q0FXBhsi})G$A|B>m#A@zEJcYf8+Gu^M0Xq`g zu_Muz`T+JKrlv-vx>E;ZFJd}&B=V`lu_MvR9f>aNNZgRR0PiY&ICV+t(bQAepZHDc zmDDY%S5wKy0PCSKQFLxp4Vi#gVyokFHC&zE#cS^hkzvc1PY&u?%#TB2!9f_}DM`9TE zC5C5ri@%i}f!&GAvU_27;=9<9*f+isI}-cF|C;?6_9Cvzc4fQb?{i1uTJA{vfIAW^ zu_G}RyA}s$4~ehmj>HYzk@zY1B7V-jh+D81u{8ca*%jD@xIg>l?3b}G@s;e?;)k#c z@%4Ch_5$ohJkGs{r?40Cee6#B0J{(`Wq*|YQT#IYB5uQugzQ4R8BNC`KhBT*ul%vY zAiE5^^pDdnW%=zWj|0mgwutBg)o$aVm+@}z#CvJwU9=~qx7NmvNLk%w(m}i4#nP$xr$o_D87O}A^PNWN^lj|71st{fJK~^C_a(I!8g=m z67g6g7G|5VA>Q@eRs80-@sbB86PVE#N6X&BGAH+GgTu z_`A3b!5jSMli*q{P_KRcF&GJ_ydwl#uTy@2D=W0#-|B5~a|0LMKm) zjqovD&QLGBzyp^^BY$uzZY{1Y%E_k)`usytOEe=w3*)s{v|sVD;al%+(a_gr#VUl{cm_&-IcU(l<*(%|8?;XUaI0-z&z#s3bCXC z7T4o16mkv=E!Tv;0CXqUzf%^^qXtmNCC1-HokTw@pB79R3=Q!XOYLic!ATGo|d+AAXi5?N%WH0Ke-y@F5`fTw(1ZEHl9p~i%M8>t^w8YDfkc(sv0$A?Yev^AOpTzm0u#L<>A67*6RGJ@b7 zC?wccd=;Ez^ce}w)`>EA7xK7{9ytvcf$nbnC2i?zcNTAEnD|C26pbS9SBw7!*-=j{ z1BtmF8dB~Xi(kVCG}ez##SMZ-h-=5vhwW9o$@YIKF{u@_CNt_d8tok$6kq(4({8X<1U+7AyI-;R0a1-jjLJb}v1ZGHX9l7bS;KYB?V#=qJ)+aajyn?gGU`r?Y=XbDIlZLD0ykyH8{~3a@kQ#eI@QuOv#QEb*A-O3sGW={x>Tp>pVS^o@*SMdrWHj8qO83pws?E@+$_e{o7_ut z>8Zn#Q7c<5Wwrw$D?W$5K%Q3H*}kEA-?IWV2IHA>+|Q=jzvUOiELW02a34_8@DiGS zX{d(!5MU}r9zXi^;S?eq#}J89--=!sTrU6Y-$B4Y?JPm|zwEJn)axV_$hMqP@v_nG z4%HuxBlv#{NQ_%CTFTbqA36U(U(gHwcLnmZ5*w%Zy45StfN7;EJ_l&1r67*hl0e}l zn?#2xb8iOSJi$umranj)u78rs??BzD3$DB)7adZ_zlm zByCySyM&sO&~ZlMRyqAI^O8PSKPA_P&LC7iCuNchyt0zp@N)d2{~a6>7`Rw6^FSG6 zy(5fa*FSts*z(jlwPz|6oLw0kPD4Uoo_6OUTl#~I>C#2X>?p3IHO%36y1=KGuv0yn zP=bTx|AUr8d7CalU&1T!iWFre2`B(!wpoV8r+^W>??|0w;e2-H>JWnlN?XIABmS=v zn%SYQ07y&OgcTuKL)vb|q95gpagcUaal36zSQj%Z2exluwP7`SqVlm}Wy^j?bGF1d zVMXcZF173Vu@?pwA#$|E$|*CJK9|w734EZD6Zle38M_)9e0A8~5-lphw|IfD3u(z- zMl{(q2rrE{+Uiv3G}ES;_xpVA5%V>aK&8oXrPGULdPtbSdxQJPu52J}5K^X$TxtV_ zIih|69~;mP#@SLPiSeSi4)KGL7R05qwzB0H;$x*4QAU^+qxM%NY+NtDO%GvQ9oP%201p-)V?uV_SUhh17o6WQ z>%V4wnzYs#OMvrK(J3;&w7SKT$lYr4;seFUiI=))!Oz-=cKuhpg7uP9EQXWq#??{@p-m6eh|HFeYsej*pefb3>y9BiL!TEjv^>0@6CCQ3D z>1sK!b&E}m)*$@=vpm;A-Y6*ng zzrdsWLVkowgf%05x^nbK8_;tQp6%9x3ML5+jmq(AA}mL30@Gt=7r z?rJFyFSFQ3ZmrFuMM9~GL0p!%8|C_q6LJh;jUakL_KM*KbEdxbo7*91L z-{>vaOr{hlzx2vFn}(GFe6eDpmyvL<2*o(utE2H;{H|uJMT|I=XTagk{)RM8> z3?I|xd1gWOjY{>lO;1+N*P1xD2xKXBzsihhf5j}>uDhFLNn%Tce~cPA1tnVSU+;fDHvYNxq^RdS1hpbYBoDHAufdbUr{azaU&M` z4$l7M9g}2wd8BXq^PX=0mscWry4W_NAQ~*nz#Fx@0c_nfz<>JalxD2Jw z>feF1zfIH2VAHktV4uYjo8C~;lNdUj{Xa*z+R>K*yuK_G*gwk4Y5Qbf+M#;lM#gMh z8e?bom-N(GDesc@SQ>2nS(eX-O#0<@+JI}4{qXIJ_I1=ZEP)xo0?_ht<%+RH+pEhf zM_G>Zw$3a!r*WcavI+CGGS;>do&<8-oLDYJhuIM@7X&WHkYTVKQ1@2i9VM|#!n0-` zZSq`pOdG810}CX2u;0tp7H!wQI)M7bMdnDwdCgWkp4+hEUv!Po`u*KobQXv4Zw*S@ zYdi!beU5TVe<4ayVn<+4Vz8I2=-d4_dk^E5NS)>`l`D;e9*v6s4PO1)Rl(#J+1iDpq$nfn4KgnhSoeW3x<0D)UVOysBd6{4oaaXD0KTjI&7K`z;sd{{L)1~F zmU2nmn9@kr0PEY86Qz0u5}B3>IXvekNl!vpcH0pmyMwkzqMsYsuJAX}TH{I*VhqJT zBJvC>&i{~yN}OL(sjJzWDlrdyQ1e}UAlgl6an+fb;75?wvlv4*;>5(h+DhH|uL>nK z+=jUPuQzzTnG#}c%BXG2V|!rlYQ4Nsd|9IeXHALVxz4xD3S1VJxjBiY)Pr%8T3~<} zu1*3P#rd_lvBg*l2@FLNK#6Z3+YKS;vC==oTTa?wQz5x2KrE(!<;!~e`et(aUJ>xL2Y?@!Y(l50Aw>+$Z zy`bDg8;4i9QuE>Hxpu7bNC(;HN(A9`wCIB3<8E~l>sNT5Li^!Hz*y6D40Z_gt-tb7 zpwqBh1@2plMT|p70OIPCvz|vW6zO9f5T|weJS8Q;?y|c@1ExL=Wn)&J5z%QL$E0+o zY0rNOK^bZv?{5P6{nN3#h<^d&-ws#!%A^(Ky>Hvn`HHj_gptymS9RmJT7R&NwwiNn zE(z2kxKn zz(r2~y9;AzbLiKAWJ%Cw4D=N8Y%K`KryO8MVT2{=>w^6cj>ILgKWNgIvhZ^zco2_r zB=Q|dR>H@4i|Y{c`~8Hy=B)$LJ%YV~)!3XSE7qwan>hyH|qkV6y<10 zXdD}E@0Gz*BOzVbb}YUe&W0uLjY)4mJ}ZL_4*b+}e=5H0P7+7OyNbWXp4S?jYTK%_ zE3uV9S1r-e``6>imRF%MA))ugE-WZ5!xVikH*l;&Z(=)<83lksY^9n2GyKN^OLAp_@c56K1p)PIB017AP@u z11j)=D>>XI5bjTNz3e$+M3M^8b&z$KQlMP6fgbt+c*)~7vN=<$P>C&|FAT=!7;yXq@*J}{pq!VZgM)Em`PV}KO-){RTnf9-COutA=K)@c|gB{4{r4mc&+F2YPd10+x0 z@v^qP`kWh-ufq)tNLoQ{d1Bw!6ep!+O8}TqMrsTn6Lg!XMbvupmRO}bDXTF?F;3FP z3)z@d(CVd5p+|xl<=7PmiV}GWfr#{+3m@_%U9cMqlX$(R~VoZ~g;a$0d+ScGAlZ&IG zqcL7eANVIc3C!{-o{h0g=HixC&ty--dl|~jj7-ArHiUIkiMi)o&};ct;`v`WtNC+M z$rC=lCeU9|rz0>A5o;Y!BEfBu+&Yc~WPJ0u;<}Phyw{-w18c>Medtxkg@5DTk zdMkZa`j+%NnZ59BmxD8h;B9H=WWJWUJC5<5vMKS@cpAQoa%uc8@lEm1;uo`bW$(`3 zll@ip-t4cltFr%@y)S!z_JQn!*@vC+xe2+CLilb9O6gzV-W1;y4MA!B4DgHbi_sn^wY#DZqU7$5+E9A;MAJ}$zltWK6z`2D zpd^1CjYDa!igrhd{%3R`O7*^|9wmD}Vm*+3AexR6elRMalpl(^QPK}bQ&8HEL?1(m zKN^iksjo)d$Fh$_pFrt9j<`=`pNR5O3sDW~VfW~xsEd)&KB$j9qKT-JQPKXWmyh7v zDr0hEqDs`yzJSN&#sS_hw_ntOx|$FTLw$WLszRN$!p-OM(O#&#wx|>J*B(`)4m;r< zlshQuL0$GlS=8sj(T7o|hX9_Qn-2KU+@Vnu>i00XsAGJ=O##Q9eo_N|M_S)w18uy5oiTp0DN5TIKao}j*mWo7BL$xT1E6Bw2S4@ zN6+(;o z3E!}0dvaitS(NnbmHt#bd@{2w*&$G3oG^Vguwo@9*5^tuIe~4@T$hH0{!k-@`2Jzh zbS~74-PSsB_`flr64+Foh7VG;VJL+G*T#dqAhvlARKVx|Ww3nv`FqKcj#754LfOkR z20YPgTQ|mBuD9c)m5$Z4Amw=jJaf4T^A9=SeK_L1fn5lUYwRZ|dN1aA!k#Oidg zI%9cJYDx1gdnVn^e^7p#Yzr27wg21F@aZDN+XGzrN`wVzdc3Itk@F>aW0Qu64deOC z^FEMZ6H8?)>>X!4c|PSK&!c>Je^`@``)xR6!zzbd#fR{}laRBG89>K8nS@fX^cp5* z-2d4Mgh`8eRa&Z{NB;d~c0XR@Y-)g-H*{~yDLTE#_eOid%3)s!+b(A|4uxJnZS-h3yb(?m-r!1m5(8m#PcWzmF7A5)>9ipZT9fp96`Ay&H_Q2Nmc@RKi&YB zoQBucnE{BBL2NPdUMq9y>Z;8H^bs_)`p`<>_WcAUB=sb%NB*p}Fm2S}Q0zOdW7y03 z^cb|vGdS7EeuVvFY9+UJ{9H6zL=cM!dhjV|&N}SoJq;Ub9p;oHk@md^Sp|}j2!lo^ zpE_!LJ44i`TTM?EZlFS07FQ~)Gf0JzdNtB{9&Pk5km?opzQH>X&UvlSL0>0{Zn z*si3naj(pd#^B-hb5QGVnL6vk#qtwL`@Db729~+&^R3#FG}xycl3OVgIBDCG9&I_( z8n=st)C|=9Y;kz|pvFh;wxLYe{y~KiOZtzyXr(c(Xq5F>HNoy7pueC84oi%iI@Y<8 z3guV6j;GlN^nP*_sWINdH6?C(wDeP8!;AG|ZG&yP)9Cx6$u%`#7%PNPZ}xmbfQHUN zti*-6q7PNTN-Qu!Pxc;!n3Av&EA9>Ul6!u_Jr)M%Iw z0)vnTLkwgkv2!NIk1i`95xY#kA7xV?uI7WcQm({8dhuxYPe61Jho zL<+mZg4!rKpJ1tFRl&|tK_zSY-iECP3zQ7vik-&UDd-2VLxRysY*n!h`;;v3>wxEl z8qWs1`9lm=XJCOYV9#bx`k&PQMo4qB`Y(yyl|HFAe`#;<{tyZ+k`S-3ty`(NWN4Z7 zDJ3XVONcG+xWZ*@AW19-{wE?+`*<^0xf(3W{rY z1ZlP5*)S8A;|9j6wWO4{NjGB$X+INh7~Z3~+U?{Hq!`-1}joa z8qfW3b|DfoC2!x<1M{NE3C9ffT2uZK8#3d)ueh%_YgXcuQ`W&~%8^#IjpX7@$j$W# z<`yUXfI|vcv09Ju{1tF}x%eX74Zy9#_^)d?+Ys^xO#|yO>!`crYZUVBf{mc&_ajh0 z9INr(<%dN}BJNu}xA+DyPiih8Gj`!H@~-pegp0RI+h;ERO1ss!FXY0vKicllV>))? z9*mg9N3<0~;#a^OD`dvPHvM0}S^+s%Q4VVzw9rENgDc^%rCQ`JxtI12t@)7kd$Brw ze9Lc;T>6gIp4I;#wCm>o(zf5NiWn%5 z=xd^dc%i>nSh(C>`OxK)HioZArdk75w_HYd(H0U*8GY8Q*-8v*z8B>*1Zfc>B~x67 zbwX%8dCCaApKo)5#t?0yU!vjYi5@(Z8iM)vBOf&httzLnG2=IKxL!)(N*zkhSk)qB z@p>hIR(=jXZ5^^RgcCQSt$;(0;L3SZ&2=fq!TQsKCG3xmI=#QZzY~0a?Yuwy;KVl1 zk-R++b>GrS_P}IZSl;;V3F;LsPA(l46owBYD{B8wY`Z0PoTVv$mwY0o^!-LZYeYLY zT-=x>t7sTgaY4r-C0T>w2#x!C{5ILS07qTZ8u<8mrn8Xyi0f z!7nY{`y}re%gILM@;k9_zzW#{%kg=P{=Y*Uu_>MdZWG=s@JFmCy2!({EySBc#gJ#Jq5M~^pfsqe0ymv(tr&Li)AD%u_54t_l|MQ z-a>wBRX(`#1kCfWm5>WSQhQ>%jKCjShjTK#^??2T_Tp8fc?WBtGP;XqQ`?ef?`5|U z7f)`X{Km>#uaGa*1jsLZs5yH{eW=c`bLptXDx&&JCt5NF7RK z_ANEF!6KaPLt2%T0osSOSvQ&pFR>NL8I+`R>uVYY_gz_g1`S68is$m7F^tF(J{0Sp zHdm&O5Et`gxYF9qoK^hc#jp@TSf2AhJiNO>gFvO{UHec9g8ktQw>mVExnmDQTj%OA z?EDw;moj)BPvdUJyIZ%zI(`8=V0`aIah=E~rG#GbSGHH~*Pw*V8))QfiwJoUU%h$` zZCvW`4fG$G~gTI_t#F<=-NSCqyNZIPUD<}`eM@vV_Y@dK+#vr*Ii@#_W z>D#Y^m&{kLFJ1>%_LMgxybil+T7x1hY+hq&HKVV{C@60lV;!)k%KF$FSSc2nev4fH z8nQ~;c?R!hdl_F8dkgz^&lGP*o`e#!1GqBx`6cdnV>I(C-#Aj|-75cX62Z^d8E+P;GFUXT3OLZ0U#ndE4V(g9+h2sMEX z@%Qqr(1$z7_XLMBfapkV?GKj*4a%SCONEm2tww@<>K+pO(8PE;l69%MPR1A7;9@)H zm}*B>2}J#xx(P_tGTfl0FXan5)7#l%Z9hfr;X)mi`8p|s5MpSJ_dVR~3R6kxya!IZ zCJI^{OIx`OJpU0|(L<%zU`j>mRW6LPVR`|q;nqd_kzMbXO%Fh=BJF33Pq=cc1Li4y z@zO|y9=;v@n9mkkE_#O@@G3_4t?1Ry7>plHM&kQkta;%qS_JNMppr4W?==dc)Cm$q z{r3_y0M>01myA&B&H7I^`T+W`=s&DJfJ(wdE8>qn@d4oN+mOT>*dlz&m9gOax%~OE zfWVcL$0hrn!P|OK?+)tU)S+WpmZJ+L5Z1OJklSEw+0<^((+9Gplbm;zFD&h3Y3#BT zs9Tkrv#JuzcRts)`?-7j#!6kD z8{4rNF+>VjgTL{VQme4}u+|b@(%#VOH^EB8i7<>OYBO+#kD1!<5ejQfF8|hlUy2&y zUCv&xmiyi=wx%-uvphH0|2X4przn0V+zUzP_x)^oY$tA zYt}96xe+D6ubyien^mYf*yekobo80Z~8gOL&Yb7h44M@+s!>;v<31X3(Nwor^onU?aP0V*PrzX@pqVYVV34?dVm&zbXEk zq2-!|rGzH@cT%a8mDr7|MjT1A>_rX0HZ8&6tu4{&R$VzjZy3yl*s8q)dfT!~;s+EZ zA?k%K8eWuAB@5AnlQ{q?iyHIx4`yQaqPCD8g|n}6t`qpt97WL6fPDdf?32NLNAv^$ zl!9AZZnUGF&W<$%eRMa>-SLJVtf9RT?T^}hkZb4*@Rh;0q!#rn+-64QN|mYH3oY>) z##-(;3dUc-yW1TSEtd5!2xyV5#jk4+_Qlph0R(63-A zL($9{OG+?})rU73=Uv7!`wY;rZ({sx<`^pL$YbP-<4)#glz$`YOYI-g6et3l07VRySl#^EIrG{1&iCt-BrA@dOO3*Q{~8 zi#RxukLPln9s@N_J0gt-nJsz4;;jPd8ktxY!Ral*KP{nhk+*#1Cup^{?P1SFq_`il zzDmo^%*V{Ch?|XpZX4PQ+aOrt&5Vepwg}hg5E^oKFxOaLWNB@rFd~mU-xt)fZ7CdW z7htXo-SxB2sXK(N6;^)AP} zYR0WYTbNAj7P>C43@vfAj7pH=O?)vq5k4Z}GahMSv<$g6ZD;4QYo>O5GEP_*J|rx2 zVAG6E8&YXdE3ez}fwY_CUa8+QmwUUs z36nnB|K)7x1W9?UC&}^zu=>YB60iMaw?)nqh$a3mdZzR?b7`qM9i#fl%{_f?eCe|V zn=i@<6Hh~*=^5eqbKnoe+r_2NseQE<>Oik%>atR+t6)NtH{?m;X}&}g0k%D{4t(UT zEu%mqHFdaz;V#z@oQjk5(mWkE3}wL(FSoWgyIRjUx;m>M-G0`8bUX|CP;p%l$%8G} z|8n0cLk@iXry(ig2W@^REXCnID&lEQ8WcxroFow+EL*YZGSzQ0H>yvmRs_8etU@UiO89NXC#a{B9A@cnb zrm-Kex?8ptvu3%ZcQvC$$@}apX;Fw4A8WMh=%D?#2sx#ZuQiM;E{B(`R`7|dTI>^I zWM{Y$ywyjPlU4K|P7J_pG(A|J!_u|6y%1+a@P*O6qIT@Bt^zIAk|13ky&6uR?CVXV&))& zd>K>tO8eV_KlCn7Sj!5h7H*|Ey5@@r_QHFSgtW7$3y z;!Hf$Pq|*#m-2rQQ(klT5x};yl7KBZKMll&N6pV9RCDGL*^Z;NmoG5VoL1g&fUS{KOziB=c%bQ>Pr z)6pmKZm6libVn<3zb^WD^n>UZ(WBANqQ_DnjUGzvlWI%tm1<9QrzWHhPR&hyA~iqt zlhhYdH>Dm-{UG%)es`xHO+A&mC-s}umel>Jt?91RE9sA?r>4iHyVL)WE~LMl{(1Vu z^e@u4rmsogmcAo>ZTimiL+Kx-pHBZ4{@>wuXZrc{+v#7X|C$+}&%CiBZU8y}Qe6Zgb({Dt`P z_^0vl@y+qN__TO^{7!s*{NLGB{HtsxJ2bv8Ta`U2ek8jzdvW$-*-NtD&we`lgY3_; zv$H?X-kx2Oy)*mE?5VloxesMe&yC29$ex+oGq+dvA95efjm@5&>&oq){Yvf=xld%z z$xY5p&VDU7E!UkrH}}chC$nGAeJb~o#5 z&|^YPLJI^;5+Df)1V}Jvu|p&d zSMf?J_7|n0J_TmFP3V1_@gtNLr3-ksvK-edl$GE_tCVway$5x!qyG2d`fmK_%8!+w z;QFVqhH0>dC-M0detMMs_|cW8mFJXDw3|k6p2RB z1f)gG#OEy0i#15Q`w^Qsw}lgW7;^o77GvUY(&XS4?$RamDkpGVQj?#d3@-D9bu70IV;#2=Lv>BneG*wGbG%Zm}#5J7*sG${Q;W}Guz;&b6 zq?F;r#D%zCr0oHYQxkE$SKEv0^R>%xeYtj}l7N&un#IKgnWBJ?%-8YNp_tFOoP27QAPrf<|YDjE7FeH)PN`gVM~L*I$(UHUFX z*U!**UZh`yI2Y>|6asKpyI{RZ51qkf|@UB5}cNtvnNtlx~7Kh%GSZ*S3WfwWuo+kxDn-+{aC)PIEQeLC8s z{$u^eKks4l5&aRwc~t*7t{>MQ zN1P}0Cvg1>{VC{hzrG*WPwT&g9M*kh~=k@1-{9bu=-R zcl39F|6P9%*9Y_i%0xbEvKxK$LCE=+{sF%IQ2$V=)IZWc!nYsmA1i74C;DL}N z!Cn8+|D%l2kLpKJ>Q8m}lJ(E@&ydP7{TS~3ul`@9NdH{_T$!bRp?`sUkLzDT&R6lN8mMY)r;! zl~arND>+7jk*I_iNk$ToWFr|8 zQj8SEG*XQW#GGzSN1RL}6W3Wr7Nlhx=xvM~BS(obat-u07@_izV!lxTywE7b$(uz+ zk+RDuHj06G3=d?M7$wR$qtqycv@)X%NV!ptdn=3zq+V%MD$5M7f%ldeRYn!Qtv0G5 zv&N_eQfJiR-g=`R_cjX#(Lbl!PtavHyc|Jf2*-o*=(F{oDTWhjBSX&-Pn$pJB%Gl zm$B2>iI}^LUC`1Q#ujP zBL4Ztg^+NOaT)N-jmuHqD~v1f?Ulxrz^^i{!d+JzS3}M<#x+2`Z+u^wZCqeEK#b>GP4YyU|^68 z28PdCvsO8ico<}Yhao?7qH77C=-SCAx|Z;XuAO|MYXq@0rIF8djUcXuYdYPvm``^t zAl{}l@foiLU~j#+?lb!!lg@h`T3isKb$j5XTL@e|HCz%0NV~0xDltjZZbD1s=3+RjO#7t7A4)>YHr23v2;GHN*oag zof5kkOmPRUcbYp9lTM6H=M!UF`NY_CFvznMVSdm29`LixvvGZnxd+#DlI#{fNw$^v zB@jAK_IrGuY$UTZxGRr?Y5x@>#S4d=_mnpF&#)hWaOb`)Bjdkogz$FSw?2Y5Pn%m$rn@ zr4__q;kQ)9W#Ed_@bqR3_~STcvAN7*qrhU*6b(MSba=GgcvGx~C*!jbD+la$9Jpc@{u8L*kI3ru%4cA~7oPoc6FKQ3jRvI*B)l&$zYUD=MicHqaQ z>{Osrs$wkUE%*pET zSuYxuQq0bplq_)N7JRmfRv>Mn3)ekh&sof#HD=HGV9#Lxn8R(s=T>lN7jx(oaOm5? zgYN*7)|pA0VAA&~$(ZZiuf$`v_cMGxBpyPp9u~hxoIikPo6NHdz^&iHz5fA|t^||* zFJgWnzQpGVuMyv5km71X9{ASD#vlJaXI|tXqt*0}$PGD}Gz}(toZXKZx zsPmO-2e%G$aO+rR)jC-9jY_O~lln8rdSGwkDF*k6KYPho~#28NAw6AeE~x;7SUdkVAdG0d~W!Ly4MRr6>b zWxQ6R)!@2TtHpI4__xaZdpu^e-8h-MN9zGTQ=5hBUTq$}ov+OYvH^u?Yg(vCL!2&b&Ok8Jysb?@#A7e9hlvJJZIK8G5rx)-sT!(oJar$WH^cr*eBI>&jqKy4KZ&Av#(%g9}Q-ICvtL^ei!ucBa7vOCFpm9>qqIt^;31?`f1Gdqrvqb zK>P>6^(D*K94tQ?EdNnReoTK1-~Jqo-_49)FynWF@jr>#)Kg&m-y`cmpAM= zfScz4V|fms@f@I8e^-AOHb66hD4q#S;5ooV#~fe+&jIRq4v^1t02j{zG|T~xA{Ao) z0_^`7u8H@TGVgC;-k;CBe**LVam@QCI(WZ}dB4WIzl9mU3C8bI3XCzv7@S*Aj9+KQ zAI6Nozz7H94`Y^Z8nIycCbRrxX8COnmTxl44>K~r?6bk?F-9?)Phd8m%xvCd25&Hf zH<`6JGHVZG)^0jjdmFR%MrQ3{%-Rc>wTCfluVmI9W;7a&$aAyNj9j&VyC*YupYGu9 zVa(SJ=Ic81^?300*(lLGV?NmD0%IY{y9m78WL_S|yu1L6d^Hfs#W#V4PiGb$#wY#~#xWDEXZG2^ z3^I-xWE^wH2Ih{%%nsw27sfFcY+x4Hz$~zs*Y`ra7DPrm?zAGu8c8-=FiRBjm$C4MwqKmJwrWB?N|H9DpEGmRai2o zMd;Xml4;(q>cS_!6kn?S;s|~|5dXvv{>IOn;syNB)o;Wv5$N}qxNrNI?T9Jk;6B_* z9?x}PepiE!J+1sw`IYi(v z7;WG;LVte~dTI2~=rjMIzknX{ujt)A*1teI*@}4(=0LE!G|YB#_#GmcxxngbF-K{I ztpg;s2V+2;~SB2sH@Jc*{l$7=9~$x8Zj? zuugF5gtK!6yY(1pCdeu z@C3py5S~PM3IWfTi>DEOiSR3gUn4w&@GQb}2){x2EyC{*o=5mS!XFS`LU<;Qh44Luvk}fg*n@B`!g&aL z5za@r0O3M}ix4hGxCG%+gv$^vN4NsvN`$Kru12^9;rj^JBK!d1I)ob$ZbG;j;fDyf zAl!;@8^Y}fcOcw}a2EpVRz=;as9P0vtDVi}k=0I3SKo~;sAq>N_ zkM(X z2w3Sh@U)+SGsTQh1nd|#!Vw}6q7b4HVh}K&Hc}DN5YiFc2pI^O2uRaFdhjuhgEn07 zERKV>aGX(tP>X;WHaNyOcn8OUHHZK(1y^C(1Fm2 z@D{?`2=6K?%V>s-X2@uUjAqDa29sTicN{HK;!WBUM1N)AuMGT!w<_VU9{kmVHwzKc z18+Ox z^5ONu`?ukJ9D@-p10z}*+lUV~LhbqQsI&K=mjkfef8hF`2>(XFlSYuJKqBfA>;5XV zp{ObFIAB#wQMQI8g`_2gBxQ(`zS&})Z=bq0zKD1(tluhIdAYqvl$It% zs-m_{)OPvaiub)$5FxIQpnKG-hc-Ut_0pJ6_i3B)Ml69kHC>67N)XiTKwq`v&X?$L zy+cB}OQ|F|!S7h$kn@f$XQ2aq#YPu7&_Mpuf}*Jo^f6n)Vu=RLU+_DYIOIHU%US9` z|7xSl9B3ea1rBtn9XCp%loNHojY3mFbRF+Ne`?3gcc9yBw9tXJ+i12#hf~!9c4{t{ zE^bu<>HW!y?J_?_Hh${QhaPI<@WTagZM`i$1NEzu-Doet-Z1*$Df0-nx$u-yGP!X4 zPYOi{K~Tm0ZS}dn^@C$XQ>NJJyL^n;=L-=Bd~4MGwedqMVTHf1@tvy0UosEcOKgoX zutt|FQ9%!iw6@^mDWnzx|1GtG!iexN(@ZHXjG&7YcTr)9r#L+&#pA!&B67Fw@ysg; zX;@Nn-rg^d&tJN9{_wy17A)v{Pu;(;zq~6WYWhs?+@)1DO^r2GwKcUhu>a!W6Y5>) z|B@7?DLGg56j!T7g%Odi^z@YENosglJpL>xjWkVhXZHn5OG}qs(A{}KZeG@D-kNni zIXOM+YP_dq<>g*rdKRBMW5&6QJw-)%+3u|F)ipJ%yR$N~^NNa5MoxvxNITKc;x->9Tj$oc?dh9+P8;P1wPReYZPwG#%hAs8|7gf9 z@QJXI@oa%<*ivk|ite&vYo@jdd7;>*E3J3f5!Zx&o?{KyQ{L=Y${dLfj6m_?JS+Vv z>f8SG)ty>}Z8b|!K8^asUWZXWDz^Bpunke1XBnU+OI^acr9I22ozh^aJ1CkaQM9{& z1k_F=$q9Z(sx9XVjruK_=3+}u{RlbqxQXcs+_A)Q#}Zr45{>#TDQBqzrG87I%N!{6 zTM{jBpww?kG|GXJ^++^UqUg<_T^mLIWL-FNZMxze5~w$m67n4=^=1++bfDCmNi^Gm z9~qN91I=Pv z8f)273UzPNlG>?16RT@|Z=f5a*wmWu4x zrmAlJ%$%H=>ubGhb21Ar*xX|GD23=z(y2$OUeleGnpKvavs9?Q&q?!~Cbz2=&bNDA zFf=VFnkrEr=NdYe5>%_V0jg2$OElIYfoe&j+3V2QDG%CYlv_jFo~|e>JW1ikxAvr5 z`s>9`-*x{(1222MfAx2(-=qZ19w&$`q0QG7g`^~%Y_H9{tag!((SNOJ2F8h&CHRp0RQggWyhu4yA;~PcfIdHdy ze+Jy^z*8+8^9C`-ffq&1F zqbH4^9-2=Ta}OcqWKeuX;Axu^;$p5FWx!uEo)B0Ss^;m+RS8*fow?@@E23?cS)5;( znSGPEr`8vBTATo7H=U7H{3;uc5i2*pSdN_;OcLdJt;Pg325)nMPbs-Jd?A9d6Cxa@+S z?hE*Lcg@=F?CkEfH8tzHb8@=ZnVuzkX3W^L#N!_#>iw+SKUPw{IZd|aJyP#GgQ4!A zXsQDxrY0pUw$OSl)Rv1G@J?>$LGM`NK#8eIIZGWVF*S)UbD+f3BwFA=sRx#5lmq>h zjm9|8XKXaiLXm5pTSy7Wbr2oJJ5cI(W!!v}kXa7OJqx>Pz_$Z+$fH&?&w2V_r2WJv}sG($tXb$jTa1A0M-DcMDCo z>a)AnR9CO*%H~R6-IFzgfD33%(mtCTx%Pvur-IhyMEzt}I8mOxj2TG%p z)Lx+jB?cwYYzImss6_LEqPdp-Q@IUg(KD4`44&eWl2RAu#Wv+4y_=?sCa+X0G)o>Ns~nY;5efvF`B`MO0SznrfQ*`WqqZ?MY}}wae$3B(+H>>Ft&gwS+!`M`NiA zr;Y;}%5-Sxpe=X00}Zr?Ea(j^6%LFv^yA@p(mP2}mL#XM>WYfOQ<6;+s*1oRIWYWA z3~|4l1mAk|e3}H$zd4U4kS=jIdootfy=B#^+h{^OyYHN~+Q4*D`YoTDRH)5*;FH8Ley-F3Sl#8qfKUXcYIxK(9hX|alu&5J z9T*W8+fTt;@-PP|L#==(1ZDtm#pD{)wryt=8EL#hO&W%bho4{=6A^Wn0anjPlfiq_X7cg>ezlnN4NI1D>R~%J|si^3>^t zu@TdzHB1;XjN7%v zcbd3){@#|!@lgebGfh2jUPJx7JjxTz>xYo1Y^#-GPMuWBzDSTH$&%2;yUAB6+pf5A z#(9f9>cvApj?U~jt;V}*MrL;RnySp6mW1r2-esQd^Ou&CEIAjA^1QByn)KSWU0InJ zI9=7Jb!CO5#ug=HMW8qD>^ye~ItD@eKhz4;vWwbb3Q|uAIq`*B@`ZY4ZSBxr(x?>! z+Q)hlbXj!lzkZZ0Nup`TI4*oMkY>l#B#K&fqN)|Qegvvm=)efnJm#;t`;p5Kt~s7G z2WHRWvI}l%-?Vborq&zHo35wX@|oMWpNYxx#_MlFPN~-K=bZjV+Wcb5>1fm)6io|? zrUpd|94Ik2nOc;E)~m?BjVg|~)COeScn3;sOrrUIRGZA4hESI|EY`9A9_0W|GF~20 zx_T?NMD6rO#I@4{O6keC&h&@@Ni@TsixIj^aiG)(${ZIuP+~w5&32&F*GM#HvEY`O zO8QYU*{c&alGA`=2Dndppz_sat&4lBYa^J(ObN-3D)dHImAb}-kF)GS-S69Y#$|Eg zxmi;!(gI@RDUOI2B48)BEzp{Y)a|EG7p<8{8zCLYygGG23`L@ui^)RKOR7)EcC^G= zpA+T27kWf;wn**py?~KzB5Ie4@=Qh zrwVp^@oaiYNTh$oGDJj&_}=IiMy4>@eQ$&aJy#sUV(0z7X}P{rR0h6}%l1CfVngQhr8sYJT zr}QOwL?m2E;liC1o+A#8@kOgyzL>G%M3K58wzg>K=Ga=w3E#oxJ}k?f7z|Bwpj1K` zH&&u3A=0!_T3puQcRZ1ZrV-&Hi0P}G89<|aUR}zBdgIwuZzES!h+b`+8Q*6P9 zkr7`3XXF{;O5fI%7d4|o;fxf|pNW6qu5b^lk1^i_cirkAKzS^cgQ^=ESl709VD5Qs zZQEC_*b$BGbC<8WxT)#lRlcp_+8x`r?eML8xwaOXU|FmQbp2!j%SjWXQMUu7HES7n zzC@vk$7~7U0Lw{E@H-Yb9q&M?Rmiyc4wU+S zi55CgYAX`WmMGZ43wCO>a&wUqNRRqoicM?Q%VqydR>f=9693GJ^UP67&p`cZ%2JrS z!EJ6bo)J=(&;kzq;CC9V&wy3RIbe8*8i6I9)nc9RT5i(LO zRlXB7Z&p{oDK5YI)`nf%eA_68ESoG!Or#uQ<@ylk9J31Jion{a*lVqg5+3eYvwWS` zECtR~J)y2dJIUpCDCeb-{t2poF`DM6f!V5mQF`{G@6}Z-uF%W+a=p6-YjUTV+_LiS%&6?H!s3DAxR}bA=+5%W+S=Ii?5fVpw6f_r zC=KU=OM}jeT39ZXaWv`Gz%OkT7rc}IZ*Ve78+}z$RAm(g(81JG!^ea z(LSuW`3@BA!$J!kDB6dGW;;+Y3k%H)isnK`WJ#z2SW<&z#Kr9W0aNknh5Rn82Q zNRA;LDA8nEf1)uYFd+^P(P;d*;(YIE88NAI3r<@;O`InFxcU5;IB!hk!U3O0Os}n# zK1*w5)=hf(wp|{j9=8XJZa--H-tvl3YqR-1D@(nnXC_R)@LW7Cnmf3+zw6v39&254 zZB6sqc6WYZL3x?EaNHNZJINww7Iz=F2ds-yOK_~BlKn~4xjGv_{j0MlDd%=v^)zA2 zC99MYMy<|TXkc}A3iL;-vsvG1byjNN2CUDX|G%_A3$*`J5%k=pBCbLoY^~7lfXB*( zniB|5w!I;i`+g{vPgL(3S}?J;mS&h#?@NcT`@gP?uJNyoe(swjS4KbcO`>|>^tm1i zWY6}9bq{Rz25h z>f@Bomf`EP&jPEO0_+#x@(R4Z0PO&?q&n;*BY%$V*r5w{@f3wi*G?pUX^XaB)V6wH za6{Y50q^|6_Li|RJNoAB4hxIfqdt4#1-=gP$m->*PxmdL-?>Y=d<)e5*Nkg!?d=p@ zJu)Ztlp;zDHy^Di3a>vWc*68qTJay96HZ^;eb!a&8<(!w(cTgof4=(cwU_%AiQ6`9 z-hQTUi@N{82?Lh&>-4+TXZ3VExeMu{9#58Xs%SiquvQ4iD84cnnkG?rV2~5!P{vhP z1mcnnNi`mD2CMi`PtCV1RJ1HF-)sMd6X* zZhIv7hNY^ru@0X4KUN_xyfXTQ7oxA+^Yz8Z(H?H0bXs0LOu;bUN_VG48ob}*8-03R z=Pij*mxK&GA0B`H{BQK>l`d~O;r+O%bMTuzdN}(VcF}^Gny7@pb8DHoi=CUH=#Qgg zny&5WxUMm#>Y}Em^Y3m~D=RY6(lRPilaq(;P;VY0?fPXvyM1h1MPGd4M`;g|g{FPU zalu58o{eh98HL3;QB|U_Ehnm2=)efnJWduz-j{pPW1#iW>KC%kt6X%E;-PJtJ9eY> zEMC5yTF+kd+RMcqz9nQ-;)sN#rAEI`JMcc9cKOElk);(2ncXEI8@zmj#RjTbVoOQ6Dr)~Yv ztnf@ZE2R3@Fe|i5{V59K_sZQ%vp-9UGj}vf$`mu_TQ;O)^Re9XJJN=1W0E~t5e#bU z?#tSj&z-ZXZROlKE7~sIz2MBq$cWwMWtWS4eS=$9t=ftuLI3Z8%P;F`YwMBLgp=l| z7Pg`mFgL`yiDhLY@YE4FS&57vH3ARFi6OFQD+}6G^Y60XJMgJyC0y0Z0 zM(6floSU4pt!?e*i1>5o?)knPUs}&wU0zsTP_X@sAGrEwUVHh+A8g*dWA~d(V<@kj z2A2U#sANg=f}yDnl=3g*MmbO#OC%a=q4nx7xo!!ik#QC4W^{V-4jE*PQbxW5C0mkc zwgV+AkZ1;~UB%Nuin@F1VkMTvR*}iyq;C$lWVUN8Po~N#k zt<4>}F1A)QRf}q}NtVO>}Bav3*PqUh<66C0%slPYFyC&f{9Dtc!8 zxdi;%T72SVrtlD#fBwd6)qz!g@qpIRUNk>3F=JEp%8g%D4&=J$c0Be-XJvDZcOYy^ z*s{|@pKWMOj`bv`tytxI(r`EBOmCR(DaptwDJ?E2u14Nv%5Nh-7gCAL4?d+5jYf|K zP_4=z*VSjC^~k$l0=#O+NKWuOW;^6G+j9CHXt|9J1W;SLX&woR9`>VZtCnC(m}8;A z@@L;6jydj_>z5;rY6Z5OK?hoGqw@l&BY&qH=pX%Y%>x!nIhpE5NmD^|eJCJ7i}uGg zPXj9_p-Y zsp%Z_8+O&;stJklJG)C4G`WTFm5Dp!iu#KR`-?mastaZ&WTdRF@viO)&mGsa)aY+= z*H8C&+_@z^&F5H#-`Qz4bi11uSGdNTp2(_!{QNo9i^iT5Sqmr= zS@9jzBbgs4^jlMw!B4(ZJp}JV1a>?zYqJ*RdG$AqX0nmtrT#}JteqrcOVfthx($sT zI~p5z`0j2Q=lWgQoZ{j+Wo2`Visl+MJ7@RrsBYNV)4i+FP!IVoTeIfYilPNo)$@yr z=2urOC?YNABIWVWa@>eiBPC;#`&F#xQmVRkH#hI@YFL-xiRsBJ8!Rp!EX(VO@g%G> zns#+{?`$kCPR&S%WXMly64){`){8FOVj zvO?yofxe5>mW{Qwo7=q0>c+&3HEOr_WVOdnNSKwo;s5K4Ykq5n_<6y;0)KtH zL;I$1k`h_vC*pLx3q#h^-72vf#xa0Ct~|?lJx)LJFKyjR!l%enDc@@im zmG46u((ZC$cVD;m@Na0qqJGwy%?;by+PBsVU#Yk&wxF-Hq(9%Ys4BlV9u1VNuxn>i z!>(=vZ7{riZeihIh1CW#v;F1FWm|e!TB8~aJsK1}Vxg&OKmx2${S#$DX79Gu_#a6x0%(L(D8W%p=oj0sz?rJPfp^D^zmcIeT%7$ zn=&q=GCM3@?o+h2Mx&c7^D(O&bb1HdKRg<8=bq$W@K$0E4nLn!LZh62m=n_|qo=o% z{fj^N7Lfgy&d&ENtS*=ppOLy+o!Ydst7}&idL?~=;R&bSYOs8Qcd$4)H7^tURq-~1 zGVynHqG{r6OVp?JH}$PAaCy8V>XQh}rI z1n_X#-1^)cmj&^i8*ENJ01T)JGY z;p=%MS%&AJ0yv(7qA`Qs=Bc)5kN9JoM~3Gqf3xwM{kSU?IE_=w*stT4?jMhOkj)BDp|6O z-(tnD!t+jlw)O0GNjl8 zCi#8!_JeBSiIri|iG`UtE#1$Dgp|Y-7O2HTe{35dn}IYD#if_h8d(~cHEd}ntl0l7 z6wan}$!DRoQgO2?S_b}|@rM@C^L8%I)Sf3Y-%a*?rXCuaCPGSmPgpiLlWp-oluN%Y zD$C$ugj{HYa6FAPo_Rc{Hu3+K8qAMVlWh*RdB|a#$co=KITx7r(!2pWM2n!(!6Q%1 zjd0WaCQy*q0B=GHK7FuX6|MM|cwVwKWm-SnVQDOLuEYcUDVJtEVTc zrEz@J+O`?%ro={W8{NwUx|@ktM3>jim@&S#o7ydU1xgpXIYl~yxqpD){(f!hH}q?? zGUM#m8pj9e*Ct+tZjIC6evQTw_`S)m!JHSnkkqZ}$9lT?8cg04zMNojk8oVFqZZC3 zOK_@Iawk1ZTJJXmZ>!*MY*rW62qGpy2El zIxm3Q(oOS}0|jTd%5orpYSV4>eFvIlqyMr{$~9`oN)5RVqN76rltv0GwUa2Jzs`Q7 zq|&o3&N?IevLD4FcQCO#dT`+D6**&PbMvklF==U$neGW=awkkmsLGib6Oz)o08E;Q z>YQ?(R&&knXj1jeSnDCIWKX=NCdHgTIJ&Awlmev6QPWi7AE6YCQauHUJG#VD0+m?$ z3Yye6@Fa{^=|rnarl$o>SBms8(UvIq>ote-gBA9dH4w=7$_8MtloB9zcn& zSm=qM=vS8Qq;h+AE2aMV+Q_X!lJ}38m}#S2F`~cF8|5t)Q==!6lDI$UYw>!+{X4Hc z+%-r8dZ&2$G(0>Oqp4CYO*^{#&q<8Q&kXZFI2Pe_)JTo|0(qR}FOO@Sj$t3Y2%*&4 z)1+-p2xt#;Ijii)f|mVr2ij%F{jUQJwB0ZK7N_~FezAFED+|`342SLE`)``>igHU~ z+v@ALbvd-8PMNv8v2F`bCH=0H(!K(ArNm{VoMtscC~tJDtNE6hFPtpo$*hKthkE#+ zo=iMN7Ve4+`MM{waYJ40h9=9C+2|Vg+z3zRj(+lFlDn<(Y4w#`SFiEic?$JdHNUuc zeieH%{V9K=50#!@``;?X7EfTRYc!JEmVAr-JJ`2q7xk1_xMuaOLj{t_h_#$9&185_Nq9233&KN|HM$~B zo1u@H`peE%gICKh-lTQ>G6P_Mb4(0ZVkJJ2JHf=N;euwjbpvYDib z*lQiJPs!NW|Kp79=aJoreGd78Sc+*5QEXf5Pw8Th@EsILYbH^oxOqh~T zl7pvDVi}yt>^W73e&Jl;4GF#4nEHstM>qlK$ATDcI=H#$MQ6J8@sv|DgqLu4| z7Fs_duHr!balv9x7SqHXWKUpc8{qlDj1l9!<*T7F9*1ACtC~&AenO3&ruQF{Wr{s< z-l&P;x$%V^SxdSqdKb=Y>zdzEy}r3=c}a3vQ*vzWipuJ>#+Wf)mn*q0r?I_C^fXqs z%^UQM5uf#LC@btQ&1or^7N0v%Q#%hCQ0Zv_u?M;Nh8>wj;b+Y1lwPQaoZNlqIebt< zk4g^|d|9xio$4OUC8x^q2b};dSo1+!v^8*SdrwPVc3whsc%|<{(PZ?riNn6g+`^cm z(5d2$%1YE_1Z140?$OiLCd_JFt{KCZ4{ri4WuJo9o5)sNo&O?R3Bj&3q-dksON^r4 zoG4zTg}9R_cUp}V*JU2Sn~t}#jQSC=6)6L=uJ1b}n=etFEkvDFVYUn}4RbTAfT%89 z$(<_RhUMg?DV<5vsOZ&3;qneq;CrKQc22)ngbp3MYFY*PXrv^v{Tb5slPQm*(W61p zBSF!_LD5qVl$fnd?SO^WQ)&FD3y#Aec{mh6nXO6*CvEf^^)5>Srltz!=a!6XxIGPw zC|$iOV>9YV&lxw6-Zh+_l;BK{7=c8O*}16V)MVa1cA(S`N_5D963de4@c^n3dywdt z7MiNgvh_o|Rj{McuOSVqtyb($12Sk1W}%L}F@KYiKTjq*5@>R9up>;9x5}r&w2wY) ze?8@I%^#@~ebM6ILwJjoG6wW`6=F@cW6|?t{Os7vziWIn;TKhpCp=#Fe8bPeACh;; zJH-pW668|e{Ws)iCF+1yKQF;J@OAv~2pk?WEB=8IctFk}!jZdBltHs{7wXcABHg7$ zT4`vCJJJ>Uckyb^gYge`y(SLKd_4A1k%(8A`~%Nn98UKgII4f5sCb)eIctDD1NKQg zB{eD@KbxeMLMf}#8lsZI^5Rm8B9q6w+3fo&!c)ESv-a1YkBahyhPw-9jE9;YE%HV` zp~m{+Q7@KEYEyWEh{luGf}w{UD76R~_kaVX{#v339VoREMqSFBU~&%y;?pi_5MT*y zgUY>Kmiw<{rK8a!4wOnL;~tbKxCGMlqb>!`NL^`19aZ3TUE1Ltf%oyS2bUfn3+3^O zhyrzhCPB+u7GK(X=WU7J@Ti1>tlXBa5IjgmzFu|FnP;AQ%XT3CzPVpZ$8%&f{*h0w zoc3<6@HN?u#&#~JG(w`-fuoMdd(yx?7reJ*v2nrBqXCq5>Zlyo)n}o!I!tv>C>TZ@ z$q9bPY=@j?TTZ_NEw|Bu0BTFePMx6WVH-W2y)rU?b1XDi{sMP2IPREh%W0ssSF4jQu@3A>d4XPi@63o+>I%oB?Y^+y(m4>)|n#i45Qa(^vy11&KJ0YX(Z10+x;W^*BN2g{>#~jbh^uk`p z?tp%4gAQp!mW8bMdRQ-wQBV4{q@U~9$MCv(H@*+-W56Kl^o==-C)aYBayHC53tJdE zTQ)XmKCdorixGLVO3DU!5w{mJZ~rESn#~<^ie)b6d21IIV;fOswpCZWMy1{)TcBI?QWFt2(1T;pBK$eVFf<{=qbN2#2X}~8L)2z|nxDee- z8+SUJH#KPDH@a`h2&<2Gr`TH;YPYn_Deg%x>?LA&u~R4?*gOefb3oU)HaW|cD7{YOpYF7OinU-TGYOwhjR-Jz0xpYH%*wS zmD2i~`UuMJSbqavOY3ipo8~8f`{!G z{k^989bN5Pn>1gJAs&no1%2h^bBoHB)D+E5n4T&(3GQh6W}jlPo*6Dh)IR2*Vx@l; z@2EGbHVs?Qzr-XymnQLH-m8E0wJAJljppiWc@F=yHHSZw=kQW8&EcO$yoM_%v(3q2=LxqeeiYuAj9miDgBww6unJ61+Shc7dBZxhMBgA3*kE)>&z zhZYXbT_9q8f8Vydv8%J0%S35V&-Ytt!3y^L1i=rFz^Sj5@eho^19A=#&SfZPKLo}> zoQykyJzEb81PWH-5nr^NxvO!vJ2iQE`Mmx~(W{$n9|h)(*ui6M=vg0LQCGOUQ$+fX z_RY?n?UgyElqd(3G8n2g*By!aeGi0EDP&x~mw`|kF(i7>mhc46I0&Vu1(sME8Y$WD z4FJkxhLqv=22flY9VF_X%M(g{s6;UjhgD;iA>QU$73QtZd6gUPwn{ZT&pEw%E$pU; zLw^vMWF0OqCtXMxq>H~wUA!3#J>o#A)H3ctiNa2i3qR^o-lV+gm~7R&DVDU;lQS4zIWAh=U$*`>*Wbfqu1(jCeAZ*M-XO!=jW=8MvbKSX zAqRfU!Z8AkU zX#N6s^g8aCYs=}?=Gt-w9q39Mo##LU`8yRr)nop+=zCeKloQqCM@dsbbbTlwL96q} zHBUNFmyHeuQ1u-fJ#M9zinq%7Q=?f`I@__V@isg5rvVw-Wj6X*09A)=^w==o14K5X zxnXJF^cIXrdAbPRf-$G9HplmLvnj@jYP$}hZVI4sZFL)2&+l_77X(4gDC`g2gd4?OV=*!m^63Eh7Hd> z)7IJ9_R^E}ZEf{GQ9YeaP3@5#ZLK|J1;w8Hvcmj)c=Lsl58sMM^?rxv3>NlZ+}(X~ zf8#oLZgO8i{Ti=#O?^RMa;|&5*>PTP@420snRzKml`HG&R#hZ|AQIzZ>6!)&g1gh0 zkWH2_8a-m6sgx6cT$jcxKl#{K?6DpJgLJuPSV%|5%uTSgoAOObOoYU4K5qo-?X81mt2 zd({V&>gZCcJKA=RQXYw+Qd)6Ag*3Lj)v$l$z#q$nu95D=bIeW@ua}Y%^qlM4Ul2_l3P_;-$&!20fueu1(4hcI{gZ_r51`r(8$A&e z{Ys*!foMBt=*#F0IsUwnrCXAZVVkaO(W}s+(NNg)@lE^IWfPdBiq771+VI$;C!%y| zP1WMkisf~=eJN?_YpQzB>&%+5%Iw%XJE9`7VO4qgirTR?D=OpS3scfM&+VX^eiH%7sabS~9J1M#h{=d%7*of4FM8s8v6d5A5A*CbZA5&@Kw z2}kdYCm`^52O<}9O8A>S35zlQ>PiPZUKgSVr`7IAIzh}~AdsbX!oe(n-DNhKb>}ncD z%E%G^VgEUeDx4!%qr`{r#}~c;j(zMhKDCc8xv!J)8!Q}kA`-Z#ktO@5h0jO)r&a0& zC4SJtmtxoP<6IL`jshGS8vYmJzwAi&kQKia_=nsVN;xMjT$YP^Ln)_~aJ0v{(C8S{ z2=5;VrcE)eVtGnJ@~oV$xva$?%A*>?@FB)=zbfUoSm`!k-}@Vm{Fe>yB+rwHLz zB;z-8ez3xd{%a=kGsUs5#Y2bQVz#`mxPMwscvb4kKZ(dWW|liAK4qt9^ZL$NxBxkz zJ7=**8leNz)p!%NK(g5Z$n)=P$6G!$duRJSn&_E%D&rTeqUUIyh`h94^!ZL?`c9Hn zicpX53BT13u-s;nYaWSUt4H4exzH!0E;YiA%XUmC+dFmuQ|fH*N#SUNm8!?*VwTy7 zD4($8cEl!s*%G!jBB~;HW-J#-%S6ed$kcZy$I;tyI)zz)6ySgtQ_2~1pu{{SI?sVpKP_{8Du8ltEYSl2l-47xxbHjA zUK{95FfokT)s5Izd;;~ft<_2s>YP! zyx6(?rjB@T_LS*U^YM^!_eFD1uin!})T?2(h9*v&8j=;2RuZdeVc|2Dm-{Pvl~+|O zR$y{Sd$*{jujD%2IHIQORpb|?V3g`9NZde)ujEljN_BOJ zk~$j-pggKb^mqW}F;t=_f}&qpT2JMcGGcEvzoBE~V-k90!{am|sVSOQ{o2m3=!Q)H=IStKQ)4|nk37N$ zCCl#W2$h-!%C)jv)`~r!zJhfs%kI=E&0i$?xkDq{Z1lelG+^~#Ku=(*@S|)*pB@h1 z1Gr1@H*zqo$62j(R-jn9;IbJrE?cnY?Dn;lm22D2o(iJ>1e_{&ihO&bUf-~ylzb|_ z2Coos4azxl0n)MF$6?R>Q%b`f@8ekgcKhmziq-9pB*s24tG|EN1F@`Bes6 zqp`Dst?GPfRa2SGj6#n{l;=M*T60{NI+g5l!Kk>WWL)rlSE8>M5>l~%8y?ldqHH!m9OIqr7B*!PLD(h-UuWoOzNh@63 zT(~4U7180P8H^;AA)lN! zYvppOm4l=eiBhYQ=!cA2JDN=suU4Znj=uMZsr5KGK4T>HKiXIM%o(iB`Vwu_j3$3S8K);N-;GztYheH7g zJibZ^CvEg@9-#@PGv_p(KTtXD=F-vZ6n--LYKkMMzx$}t(8g|WR1{pp*Y?*5m+?! zh*Qlq^|jTZ{?m`;3n$dc(G%L&NuvNUf2wDDTx|aIkQjOVgnX4&oT@r(B~TYwov_OA zX&|>e0?RTuwLpD^L_g;;OhjMVC0m*G770y^8=CP^)+cR4m}DMRrwqMF77g#Tc0S@w zMr$1Pwuuj(>3BH(iN?oLA8mf7;9gM~g zJ8`y08UKJ2cgQ(JIC4j;kWp6dLc_6RI0ZX~J)!)riU0g_=Fby<-t*okvz|$KR(Oj< z_OWBWH?w`u3(Yr#doj;fQ*iHil*|)p?U^nO|7_BiGhR!7t^N4KSM%}0qI<=FZ$tj8 zC=18uJp5YP?nl%zN27-WD3?RVJ>Wp8*OTZ$iDG<0KK!Ul`G{(GwA@1h32gmR!pQ*2 zwky%&4wTx2M2~$$8iUQMroYsIcS~f6i%j26@uF|n7;$61cptu1UwkzC0{9Ltw_N@H z;1eE!NR9(N5Z+h#)vZ@VjtsAOZ=lAr? znoSRn^?pHFo*Q692=uroX^;wPMy80xF zJO<)oM9L&N!S9&ukVD*9%ISBYOKfz&fzsSh=I=;Q^stTY;WZ8^caB7Z<}YwZyW@_z zww!ie6_IiV9VpFBB|6W62J&|*fU*_KTpy4q<%F4nlmJZysewZQ32d)Y!bt~8J+MTF z0w`OtM2}mkrK$_<)M$M+j=7Djaq0nO>`wzSXnoc~KMSDTFUq*bGHG4`wx})UmN4>- z58+>b1-H{WtY-yGFb`OEU(SKj0G-Xm(aom0JN&`0NM zv1GQp4`Ge8Mtg(bd*)oN(--k-9qqK!(ckIgTF3|Iu(u-DfdX}d~r1oX^bk|f|dqYiE?;Lk~b$C@m>inkcy5#WWs_9v+ z*-;T?;b9dy+4+_Eg}IfJV?&W+&H?B66WM+~r}i@%JsK1}Vxg&G*p`6Rzt6e-1ij<1 zA5}ZqYB^2U91E=hnCq7VlzcD}9dw{%=MtUgK*`P-HBSXlwsVOd zaG=y%OZ1?HB7ba;QZDirL{oyrfoDH-V->aF$3-2U?4d!ERwY`4}Ylali@GX3k5a*yh#NH6H~iu4AeW!p9v z%t?rfZ=K%L9WBzthD94AA}Yf}+Z%mXe^gaPV;cFZiX<06ZH+N%F;6m^0Lj&vA^^2^NO4zttILP zx7<-{i55C)Ezv^#Yl$c2TB1a;mPlvV%94ORSaC5<7IwRyBa!9E2$2CtnQ1o4vgjVQ@R_gBmpDT6CQXMOGvlFn) zIc?q|dfOr`bK+%S{#sZ`eV`@1#mY79<_+Y!?Dv7VWS?APu4d$f*68m2ZfkS{+3uW# zs*u4<7rI_>A+W|?SsXmm1F`rD=9+Pjk~iYja4{%BHrJp)pbGRPXwAzFWk?-p-zWOg60Fs^`oJ z-pa-XqmG{J#d;Z~hO#Chbvg;@ZGSHCetYjXzae7nY1>=-yL#rgHibs7QN3qu^gSqg z`sOa2>w8l5Tr{z+jxB<0dRnsm94`4N^oT^^cSDNE3CDG*S+)dX{Sq|~%DCW( z&bT!7fab7fMR|snS#$oK#z9^Lf!z1c&gza%%4}UWvJ}tHUzD8SDcvr-&cayrHKclV znG=^DuRdU}U^(+*I`U06BujoQNA8UHKKtceSjWdaHSiV)n~nuPB&SksR8wsy{K)mJ z^`}Se+ZT2ED%Q8*|j3GTcuJ${Y0MD2c zRVB)K`>#K)dBBoTKO(Lo<6@7zQ-b+Bs(14IG@zHpRt|W^Mr@6jPr28@R%dt3n#Z4RXHUDd{qs^|72V<;H$csd}EB#6YgKyzOkFR9JM1#SFg(0j5^YD#to!* zGp8pdIMX95muSE%)iQV^&=F#<-4B9K+Y4O=Z~ch=W^s{tn~7_XZivuJK!S=u%W`&e6h z+FEC|?p)KcIy^G$G_z=ByO`sVal&2NZV@ z{IC;eJCX4ZIB|!ZLxiIxEF8X9#hYBLr$zkx;J)dN3~jBO-ASoS%KPU;M4vWe)h^ja zyz2*YGD_0Z=PleZuC{L1re8fxP2~~arrynER0d9i)8^d_c*cV2W;A*@fO7t2+yf4j z`gDmNw9saC0b4(z^o)h6Xh9?&3dmr4lrl~RP_`zC9(SN*2@*Y)Ow1zStqLVCUBF)@ z=n0Dycb2%o<=X+b)o!@0e625pJ^7c=fcFXudtTzQwg*dJfLvpQ`-rqL8a)y~c`TE0 zrH?KPV;Os}qy!foN}(###r>jP{Rm!2H%7D3?_&tZ`xrp~oilcRh9O$d%}x4$X!{Pp zwyNuI-6u=RbYe@gyq9dt*0ild=slcF)*J~2X${&(D40#THnl2?hJvB2GYD$o0qt_ki zm3uAPr)twbS}xI##i@I-Nw#nMzPeL6!SC;r$i1+`XNxS$|x!%gCrfZb6Zu-gMf zGl**vUO+}HrHn2N6=YlRfsFOCc_gzp@qCz4d}bt^T(93E$i&(=ckDfH-!o4S4n6WT z5@5bLvTS6`TVHb_3?$yd&1hkPEK!O|&-yP>>06k7-R?7o;x?bTd-n$)^!D}je(-E- zcX#Wv!u^YTx_guQdKV3OE2=^PPcRS&f|m$J%7v_qKd_h0lR5|<0Ixf4UemuNXomV^~eT$Umu) z5d761#Y3;Way8v8L=cT|JX>N2@BBB1a5aZ8oyWLHrON=fREm2U0Bs`B`Vyd$ z);g21hJ&~Z6*)?@ZUgSR&@-Ky2R%9MxI;rNKk!aDlZoa(EC{{Ua^i+ACM$omrl$B0 ze|YY_mRDJSj<}lzgF)>)wrD)W_U4E`!Wo|fK{Hz}-bk3zPkfHm;qPT;nQSZenWbxl z^iGN|a##hPzd?!#iQFU-MGMCx zUSxj|)>vB?XSaSJW()R;N! zrCPOkIDU3ag0Vs=kEw_WU%r`{G1`kX+KE-)A1R8^vb+&3F5vjV^z)cE{=KD)1u|J# zSvXF7Sueh*286E)*{I(r&SU=`zcT{mZ#XqJb87TJ%O)XToKm(I4h4x*9*$x^0%hD5 zp%H*(PfdE@b>gwi?!lsu#J@JP zSydXX&o!HUJ$`qYFGjOj@Q?E|L%bbM4ZHXZv<#C}DxP>-K7=!LI`~_Gz>%l~Efpq% z8H|FvxY!OQ?Vv-@*$}7vHgg@3-*0!`v2^JjJK3yRY}Xx2m)x;Km_1u1#IRMDH#J|n z5;skc9&Ks8B3xA%K4Yc^~a?*B^M-qqgTEZ&7SfN}XOv@wtF$e(NjGJUEQVa{39%6l&|{fu^Y z1g092s9xU3$8?Cs6my|)HJ3Qx^O!BX9Ppz25PKQlTqnkC<6})AOE!vkih|F@`3LE? zHaAUTAhK@f;NX zMK`*1cmqi|7kIFo^Wg0&nsYCHUx(~|l;u?xMEpJDemXyqzT~4MCosPOfZHk`i94C8 zZub(0W6AEi)^lxE^BTWzq~2((AMyFun60*R*_N_0aldovzQ)FVOPvn8&0;V&ttu~H z)nv{$+w72e0(8h%!l7ud?LaasmNF@ltsc#62yFsBqkKSrV`q!c)bFBBch$EXwpz_& zT3+A>Un>XRJCy`|6yF)qcI-WT_7jf}E_?WK@k{mw z5+b~?ZfxCVZ%5mYZZE;S2vl1>0Eqed3`2 z+)=hpy!aNF$J8RErtr|r771tSlkCG>w*2*zEJPvSMdJSGjPe`k{Z|~0S59gJuqFVe8##9Oa+uoKYxtH!-Yu5XN@F&pDQGsL zDfI5Rs8JQM$CyB7a>lB1ZJ)S^b%)Deuqdx zKP^_&utU3-_d7OwSlKq`j&sMKt$nSnH!YvH@889St=LV4Ku73p*p;8q_kvcZ%yrE-x)P!|wE7q%2&#zrA(;fW3Ih-j>-3vn}kvtvwJ?Mg z(>imagnL+|b!MidhoWmtc{WU=$TFKEykX{UsY!TB6K3+3i#=|wQJ-%!)C`t*y7q?K zsUuo?rYPB>6Fbs82}PEizL6ESg2tnVJ+WR`!ad_V z)utw#bs23j+uBFHo-Iyi;F8+f{VTklk+YS>OZGL-j-Tz;21d!CnnqDk+5?n`*cL=N z!A=wf*tr!i(GQhM^phxif{&E@X87Br5Ailo!ep6fqY$$I3krVnsDGJX0@dU})J&$2&@Kh&}gqT1xL`uXl^G$WW@cHC6-~9Be^Kxn2BMLh&N6 zcfdv1BNn%zgJ;vUM?Jq z+ON@E>lJy`OFW(>)p_Ih=T$HDc$QY@VGL7nkqs4r3*~&Z!r{F@8but9Sn?&%h$UZw zIcutvMl6*kPAes!qg1NrrCk2As{yy(PamVecJ=$~)QLVSJ76 ze>5)Mkz7<%vM{jGR;gzn!;}?d8c3rtv^|^qY8hOI@JOiSipkbN`w&EOI(DLA%Fj9< zf2{Sh+&9>0YDtPFKNr%dqSa_C?yocDhuBJ-SN#V|6MtqAANj+ASSJKhcGr^S4oCUs z(WS=1${di2EE6BQ&?DEZ-1eO21zEvN?l=CYTZZG91$E8P0-sw+o| zB#++~c>JQ0*E6wM?&0hC8x_NEG+vea=k&PUDODuZLdFdpZ1e<6kup ze&cI|OZi_&SU-Ld(G#5E!kyo`4x+%P3u|Acb$g#OO1 zKJEBN*{mlUQ62XHzrRNN&|cvPekILCeiOoLEtHo%s|K2@R%~*%Esi4ceXh7dtXw=o z%8RHD_Mv<>+R{-BLU)w9XZTuP}ZMkk4 zL%(;y5>v`Pg?z-`80Izw+FMOGFq|%?Tou{6Ijd%WPtX3Ex-*t9-~2-+{HJRC62>kc zIv5Ha91>Tvt2S-eut^-|(?W9I4V?=5_gRZh5?kx@5OtqXlh_0rt4ba)VTk=PLx*G5k5&;eAp)^f^Q$&c(+s_=sWC zpKink$i401Jzcn;sBZn>vJKa4+jiPv(gVG_ ztB&lf+*&r@vLU#1_4>h~we0qN7nN^m+j_3i6fjwq_w}z_vrMGybi{OBB(qotzbR)0 zZhZSG#|NxNJmcd0A-4JQ|9n%iX3cb;%$d=s#-0`o>YHEGr-Ba0gM)0U|-&Uk~E$JRv+UjjFTCL?)i)VG?dMp6R&-IlY zEG$XjAcO zG@jUri#ww0AIbL{JwB_~lY2wn-@Y{{uEA>P(%Kg{?G0}2@b=h-djDYSQ974}&f)u{ ze`guu-&YS*E_3?xs;t$uM{HzSPMnxH$_{Y~uK*t_7s{Dd@$jN*PwU6Ymh+iab9Yb!OuXl17O_6dF*vw-Xikc~}}0IP}tdRHo?ccs$AX{BUiOQrfqDX5*p@GrDLHa*r+Xu~nFU9RS^ zwxL{*r2wkmOrH^0yWhlnq`7~1;8Ir0)(#D=!Nta98zmwYUW#%4XyH^Ip|@%N!Yk zoMp@ntthYE7_2V)@qW=7Te3$*VUd8 z^xAveu8txb(opwcpGGbl0eBfa|3)NUL*k2XzD024?cNp3T4D!!hF0GElgcW8=uen7m0#;{F?1hG=T?rnqp`Q`2 z5jDUb8&?XmwR!2`jQoT-auwS@{+ZBUDHde2pKW$7?{vn@$9?Xr+_#I`>mPVv_imyb zJ8mH==CgMP-_@3WzZ8?Fn8)EeuAF)c_tZv8Uk9Knvf<@dWPL zeWfg)#llg^Wa-$0dQs5QowD81EfIQEXU2aaVj1vplXc0pNNvHBoK5QPCnBQ~(g zQz%sP6`2OjEctAprCZcM;=pfx zI4s^J%zyjs@lW4=8{;Vk_0|&g3bSLP^FrCC_`F0$08V-yAYTF|L_cq+)nYnoE=Nwh zvv^IjK$xeN7H~d5bO&G%@bO%Gh~JN9P;cTxcr0>+`G8=I z{BDuogkh|eX|^F3HvCG(-Mb}7INYKThqt8&3km%Oim*taWxN6RBZr4ptXuVT z_}tq1y1H{&2kUC>ScFiO`S*ygu`;jM%@Cg`-L+hgc6bC~B+3F?p+KXW90IVj*#2cB zYgat^LKt6k?)njGojE zhZ{S(TH)!%!()RliVuauED*jpSX~{wn3XoyHgt`T3-kZ>x4(%e+(m`Oe?nh2=>Jza zC%GY11kn1|$Wx`ph~Ryp7!26{(LBmh3`TT+jc;<`aLg0$wd|^`-PICYn^&OkEG`@L zcm~UgJM{&5YgwApDZYyI%1HE?pI?xf?poq28*pc2733G7FX#keE-pvmebL^PJgNdF z7j-nH6fjB~H@C5(Xjkju0_udzQ4a#~C2}K#x z;mfK7germ%vXw`%N}0vu09qD%)EQjr4!6~N+Rfo`Y|#k!vc^cFD`YoZ*mU?$rF>)Ka zx{Hguh52s;-<^@X+VAKu^Y_{9eg3k32YM|5vwaErq1|R^3n@B+2LaEBjX;dRz4PxF z*?8viSH$v*>W(n`{GZgeUViHX3wk;h^^Sk~r$0T#;p`>5j%pP~;bnlW=G0$nU7qICP#G)1~IHN9PEe6T|j&e@by}qUk@s5|8ZqlQ8E` z+uY%-tgw6Aia^;v|5+AT!E}%O^%<*`XMrG%)q#9TN(Fh=8F{8~i+>_Xdg&$$>@grn z0_T%LLJs%OnInZ=92NmexRHkU(ahd4z z*V@0MH8@aN`P~dc-F`qjGI72z$~6;NR)CiM7~cwhb3+x!>+~3nnN}Og{7)Y6JSV1y zwU5OI?`aT!>0mM0;)^8YBgUJ!iK)d8s6_@Olyb_QY_Z*`(5g?i1zFt>$Qm?<6Mjn8 zps>Wr9OKtZM&a@CzWkX)OJ&ESFBB-c+H zn<22(eMg!aLM~rr`TF|SUE!PxlO>c_I&z>+S+#Fnhs`})5m@SSFWaXK@9Ahcl%Hid zqbjuAgDgOWbp@tId)2Cnc(pTrwyk9>SiRC)G87DL>Mq1Q7Xwzy!t7Lr>sW&L?qw(2 zp6Cbm3~3GM<7Ys=qz{h>SAZ)h-VHM|7*!upZv!7mWZy#&+UrU>ESd7sl~v_SoMLL2 z{WNRSTeF!13mS|XP=Oj-IkuNGXlQT_S$jX@GJ>y9mxpOAcps}utk`LFBFWSy*+X~} zD@*8L6J|%l_qQ^M-y&)l##1CZLrMPA$(81%t`e;&C%@P^Q05Aal;nlP8zRfjN7Gz7 zXy(x|OTKg4HkYL`_t9x`jA%pws8@0t;npeEw+8_QS@9m^w2$Trj@ki})+s6f+cJHc z#Ee3dRlE(BXC5_`Z0Ql#ZDWocBdjZ z+_;8vS^&pqAi>7U*2T;+V7z)vofv+wv-kRIyxszjHbH#iHm~~NCK^VhZMs>OLxe{X$CLLrqpI<)YopRU~ty=r4@F%+?`10S60-Po}V+B*etZY zIcA>1siiFMi#+puiyYaM+6?eYeG8iE|iMWlwnDx1#=htF!J&mB$f9=C!WRp5*MC5|dw zy+V9wTQe3c;NIqKA&<4K#NBQc`>gFHCGA#VPqN$y3bNb)1-EykwG5Toq|)br>4-mp zM#udL$}e7?Dy4U&($}YzPWm2{?@=kw!So5`m{O2LMsIIWDL8~Yl_=$YNL6!zR7$H+ zdKWZI?~W0E=1+Lo?=gc4@C;-t_iRR@GWcB12+P>c8pW?F!KS(eCY{NWF!}?1=r~e4Jx`EQoZWf6N*(up7u0ryNNhCZc^Qo0HmL*SYOXK@uoJDkOq{^_F`uDP*=IT?*>+bz|rnk&*xa4kdpc#OGNgJ~M45+jJLR>~!Z|RGpJ+y%<9!(OT3>wPOk7=tX8$zi=stve(;gzl(&`SH46>wjDEK=y-G zkJa9#y&Dz-GIeuH=2ObK4cgLS9af0ibKWON+ROc5o6#kjjyIP&eiG?+`(l%3~t<&qo&m?D)&smkNuXfgiJ)w}TEF-(x z395hwWADOtCvPXW-Tex5N>))Mf)*k+Rw4NtB|p$S|4>P8SzWC^H(Zfk=1xd5EO!R_ z9eKQ{C0J1FOGq|uQe;;Y7ueI|7HG25O_q7uSf?&iA1X3BP^8XIGnwO3W9{h_|7HXJ z>$ykwF)W#)yA`zJkFJ||9*h6Q-HPL4Omw$m;`518b{o84uzRJwViIK%-lxsL6fS@l zh&$4Pzj6$l6AfJ^M~fw=!n|gsC!AMRSk<0jU2F+1b7y)Dtg1b@_V-zt_T;p@N@IaP z%TrTY;Lpy*!4X@B#}u~Co-=nz*xTo81 zycy^^@|%11p&25-<$vU}d4iU!bh45C8&wyQ*X5)$g5WNgs_YkW%W8VZs+%j!_0}*` znrxlDcmBfbOL7#5H&EvsJ4cXpia3j~K`YNY@y%qN@Som?)(sKrMRVLUPJBDwG5U}3Pmd&d zf=vx`0xf{1bK*$MLJrMg2^u+B^Mb4!e2#LA6%k$FCK4X}q!p)^h>MRrOJFBfv|>{@ zp`$Fg*yb3jF0RO`uX8qNnw6{jS^evo1=coqX>YMRFOci0HyaAe8%d4`Xnz>(lLrf` zvK<5}#cO9r9`air7puP2aS0rPzHq*YY#3>82s^B_mKk9?#Xsd>VG8TyIVg9`LOQ=uSql!~ zR;)_?M=C8weAd*uA5JwM>qD)+Ak5s&6=W9R(~mJ`(%tS6=_lxP+=F3{KBrhlk0(Kwrf&ym}7u*TDFh$NzktCnN0aIKtofn=KR*k>^E$krdx7o(Gd?%iu zOxu)$UMcM4kk63nd?b{Ko1R}88CfJVw*tR0lH=1#$*z@3U&k{Rm5rpK`7F&CnRIK9 zJv8hNw>P+3%wag0S1AbQBJnxmV;@tGe|k}JDc#&7Nm-JrS5vQ(QuT;%K3{E9DRy%q z+GscTW)9Oyb-2tGWR0+1mcV+6Cr+HU5;5S&S{yFhQ%AK(#KL6VOb^nv@c* z+}0Q&k)EQW9xOw)V;K_aY@90|WskOO4~Yh6pU>Ctbo7?Dv#FukL1yV_ka0+DUV3D{wy*UVEn&GSD21*-ymVx_OLXll>AeK zESzod6@ZUa&?`I9n-2E~x?r&xCU@JV7&7P*#kb4%Sxlu-rRcTugjsBzNqjecmM~YH zY0H|QEF2_E_m);fG2K+}Zq~N5pdgqVhTZr3HCH%>JA*lLImzd5!u<#!ghqsXj;9kA zw6BEoA==zXgfldZP6AMhu)sENhj^}z9QF1O>vdJi72QIJ1>_j zSN93IVtq!ArQPizGAhsW)|m}O{s#7t%N3Chm7D{=C($pD*^*=mNdsQ0d_9e1B0L@P zTe&2;Vw%h#+UGJu)znNfgX+@0+f44v`~QcC_EdS1& z!?*1Q#x{ku-DNbB!=d7e?1n~nle$Gb!L}<0J6Zo%sW}Dpc1Nqlo#oCft}tYo+!btu zR&~-|`=1ft$TJ-VSr(JyW(FK^JdXv&-7N4{f)Gp)-@<(l{5pm~ySY9`qIdU{U$1Mx(KDRr#W;Y(AonAxQ5^-~@uc`($lGOEEYy;)bI7 zWZnXAgUR2ki#;YhF-@ghqPBQb?M0d5Z-`&M!r=eJu}r>MSvxl(TsRk)#jOY(9b&Pg zt8BHlpvf&{v+=jQr3+&hm4r*)t!64+S(-6q@Hz`MX0>=+T6>F0u_<^jT54cVJ*|rSwTN1d5P-4mwhjY@@qC z;%>|Q-3P)o*#V03WsrI@As#TK62&k&5l5EUjCC3_WN8gdpPwJw-s)eHZ7B z7um$mH0%yLOEY*Ky0YrLd~>10(p8#WQEL^yN)7PdF+w?dm+bf{2iK{-P^M@&RG`n^ z6zV2t=<{>LGug4{^(nh{mTXWK6?pX4hCraf$rhvraB@wzzK0I33D7Ds+(bd=GUh|O zI5DMwRpoSJnE7l|i+GViusf-9p$=7FXs_gAIE$_WNhwT=x$<4?;dvuSdWW5AgbHEbJVM_wIktfH|@AnSU=_{Js z3g9(s$|mMQKN1l_dL_D5gEjEfnufb(SbP7Tiq?yk*t)XPQmPFb4k>0Q%?&K|RFC48 zj&O0Kc}c6Q#cFI-BB%n$q|M7hRYT%v3vS^Be<#;GnZCx5`@8mS1JgtAa7h zV4KT?Y>apgQ9j&d(g~K^nKeOd&J7EK_>yo{rvAozdm7mNYjH zEXIjlzpvA3?ezH<7nU^ygZ1^nU<1oGHoHsP&F1z}ceAlD$dzb zieOzJir|W+Kyb(g1Xr63Ars(n0-lY+z<1>3j*h)`b$dJ7>9gHWhZp?*L6>V#*?8Wf zMdvk2pB2b;g;XM?V|B=THO0$4C-ZVCc=@OF9MNFHe89kYIrp@@tN?#xv7Eg81#K$EoQyTY zBao%wBtV*t9>Gc7r{|>MDON)L2rr@j@420o0Zdwi0sMYcb`>b65|p!@^BU~W)9{(( zM2jwe20C`v)a>q%KK*q4i=TfgyRTcb_S)|5YuB#1uDc?%udjb^D73e~Zy%TPmw_^H zdY$eQ8NmS}guda@11JM_P1a*#-QTk^pB6JS1A$CoS2myR3cQbV2YmMs&$ew7hbf zUbt3Vg}JT6T=b$|rZf`~g(c0vDVm|xHPo7>&)Hg6-eEMYSbRe+J72swH$#8H*gmgk zvogVu?8&m^1&zjFZbUmc`^riOaW}B64gG8ae)Uta9%QIR?rh?+>NM(M%4c$B-+^#Z z@6JX`S5|6TbuM<@l(V$6Dn`8Fb>$W7>KrXrY{EG^ip^chmaEox9ULfDtG)3HEB6c} zRAeL0-Kv#HV4Z#ck~V~@9r8|rYu@i z=IgRryL@Gfipm;c62L5IWLf4Gm#58QY4f;R%tck6(om?>Qw2QN0>aTKo+T|2F-y6Y zpou+=nwZiP6%jm}yQkq_o`qvAKiDuG(YlKCWLwQ7qC-}5RbF0| z8EP4DRRFFtzq7U&Xxm#`ySGjH^bfdP1OBolF4q!ewAm0q>f+x;0+uljc(5 z`JPjG{_3p9iRZJPK9Qas2xJSX63=6~2W#}dYL5|SFDRJT(OJ1HtE6CGcl~OQ#$grT zk7Ey-Kguec)E?$0N2a$d_t~5v!4iVZYd?*YK8^N}_Ebj>9xmn zexIg2qWpdo{ND4w!SAEr$nVer5q^ive_qD<4DfC}MTbo1vza<1!fB}gBI*-e9|c_x zNb=+VFI^AJpzFQ;-%QsSXXeEB*afT{aDFVi3couhu43z%7r+0-e?xb&ZT$Hs{C5W4 z-^!nV%73Te`9=KqzxnTMJm1cLf5v|s@%vm}|8xG^fcKB^=U?#O)ZT^s`G5HDT>Re8 ze}Bn!ZRP}$6R;n_Qo`OV_NgZCiJNY-KG!xf(nf!t+FB3(Vm2-t+_-UY*+y$uOG{^G zOG_6hx&za=o?Uw?cg18K5&wB*R^s8C=4M+g*=$deg=QQY1tb3vt&}%8-O|?NT0VtF<-5c1s*f*Z)*+(xw@8y0nbUio%Q%;}sg-Vg|;t6%b$z zGqvMJhPU47=J$fAA~y~y|K#pV)ajaRWu+dU@xoc+znJF&ovyjv&=`}Jn6F7O>2r*^ zKhRk8S1qV;W_z-T!VsB=nYcD;k73mvU0G>U9``+w!aC27DICpVapFg_%%h{`#s8vF zW&@_}sFijKOmt4$%^~8OIDc{V>keI?=4lMcG(PwZKyTM_{fJ;5 zX%!_u=gBzn*~CJ&{EzH#V)Do~y|^E1D4L%O0WaN4XdpcgnE7G^Ku0&SCf7;sZ=l6z zJ#A)FTdB9*ge6dURaLn^6!N!rFK%q=>uK}&{BF0;7nAPP=h{GYFSk5;WQ zCpIi5^tF~+u}5BGBgAF`e@D zbRy5kXVi~Ar~Z$hrv87T^(UXttRH<2ITWcsF{AzHb3^pGlAZb-eD0k1T6V2$C9S9> zNh7huXhKFt0{#@4y2SMKL|rCOj0nfI{9WvxcDr@c_!u(1#5_xfD!Md5sW4u0pD8^t zF+DS3)p43wkL_5HLh$R-(w<`PiV4qUXcN-X60{kR3yl-YQIpOU zp=LYcsu4{C5deVz4SDr~j@VSx(xEo?XHD#x=h73j>FL@8s2;i-#%*=@ zVFG}yeb)SfqAXVk2a$?h z#wM4k%vNA@c(g>CD);|f|N+_ zI|mZd(-ZJ_ow1>~z-u<-7iSwh7DIkvhOGkK7rBLc8YT@FGhHitUUny|CaTyvt?^wP ztLf>_gDNsIB2-Z;`#>Cvwx?_)@ifp-@0rKQBwlj!N#y( z1p6eYa_SJH1-;3?4D^L0L~b^A_^|GrxmGskP~v&#B_5i?taHy1wl*znGBzu||9wTX zaq6GYL(s+ie&uK!kZcgjoUTJv;G1I2*!Q|zU1GD)FD7@nyN@6t1cbzcZuf)q%^r2T z0oTM^u(USH>qwe}Ax)B4T~NGxSbp1yHhTIBp01RhR?#(#EAcRhr$n{w%piM!T^6HM zX3c5hzcsRV*sb`TJ*NpHKFGGp-exZmR@gQT(cteNscLm9{m~q(oba~s!I{sH@x?Fud!&w@n|B83pzB!32PF;9rSF*ZcJIFqAN~k6eCWl4 z8n7uq!4$;|%MG-Pe?^l_kQP=saaSPuE^$CbU0J0|$j(mCRaNGt}+jf)iJ4kb<7+^ywrY0Wu)&4I?KEfJr$rQ6>vw9F8JGc z&-(Sp@SfwXg>mff598~9P`SHy`Ybc6MLCaUI6;A%&Elh;)%Vo_xNus z|6PsemHfAj|IWbgRQ|h=|K5S$cK*94`ds!b|6M$XAoR&TW);jzluYNod`$VV-;aJG zWm8P8`s>3up~S19zcI@CbR zXo+f|W%#8|*VDw4JP#RacJiJI3wabvHQ;Pudw>bg9J+Z2dL0m*iFUlQ$ZxbuzkRY+ zK=yyHiBOZ(a?JvFAv8)zCdF(x*#``}*U&hyCQx1zDR;3?Sgr6L%4>OfEtLZTyf3>3 za|>PAt};y1AuhN3D)THT&4fg*3x(tNnbM#PGjy|Kp$w@m+Jx47kJtTXJ*rTc*eCZI zrMj~gs!=_Qf|bxgq?L39*i5^%}2 z?%|6suDIZWx(Dz-f(7+tUq+tm-3ZWLiKi78({r?1#eT{7UA#*8OwejGvR6|rl*{%@ z7FRp8Z=IrCfj4J~|3Mv}><8?akbt?Y<8xVv-*@rf_53$IU%-DiOg@+M-;Hx{gW~DzfO)O4DT!%7U~1}vY^!+g!#H!EPJdJr@12gJ6{OGT|j)TWAT>%(apby}KQLn;P$Abrh#g}TYW9@zo7JDAoE)9e8G3_IYd zOq(sMrpnooUX-`Kw!Gh-U09TrL6E`Lc+lW9WE(SbEorF*`hrZaG2fY&W6hopYd!k= zGTL9n29T2tfXI3B;GJRvBvT(ng;^nVoEhdF=WggA!v=#=WcmuwN3J!?;LOt-)2U}; zwvoL`!=qsmg-#s89E=c!o?_{h=SD0&rcmZ=zs{7N09!^kD^{~$J!aoAafsE(MnIpv zU}KfCSB4E6lsj2B_{)j%eojgK5&m+pK9rZDe1Mk^O!8MZ_{(`({<=Ij%3lgaj^Mn` z#0|EDSqs#$h+@L&Le_`6rKjbuvIz9)LX0wp^<8gDu{MhhVZoP zYU!-pv}cCanGd-Ge(sjTtWiI9{q=#X zt~$n^W>1S|5e@^2(DpNp^iI24u2;)9@plkRi5KWa(6D3T9N|ZDI{BRot&A`O1ER^L zAc}~iVaCnGY-_Qq`QS3Qd)dL}#)DRyX{E1xtOdu|$I5*xO*ZSnZSq@BbZm4+vMJc@ zHnYLhOxYBgO_SLatmt#DZ~%Q~Mf;qz7bDIMz9}3bX_j6T-*i@W^;u1oV;Rnr7F)?8 zhhtHRttG{oF}6(@AD3^E&fz#5Ir(`w$&T~vd4?Q2qDth`#;2k?TC0F6eunbfM_Kog z?W0HV_uzjY>wg`uk|K#KZPHy(1EiUbNr#ub|j zwrnZbY-G1@Hg3j$M)8r7;d+y)ez?Rl++Z>_410XbyxwI#@3PX;W$cB`md)c|S@4Pf z*sb`-_;F+HQn!1M5+@A0-AikYzLI{AXMq3LPdoTF%u5NUg`&xMnc5JtBD{b4hCZN5 zM|eLXPScCinl3b?Or=U1BA-N#rws&Lh6o|yyg*(TOL#C_Y_ccXN)HcL%0A+&hMl60&72-OVAV9J+!ire=9wu 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 +//#include "assets/2s2h_assets.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "macros.h" + +extern float OTRGetAspectRatio(); + +extern f32 sNESFontWidths[160]; +extern const char* fontTbl[156]; +//extern TexturePtr gItemIcons[131]; +//extern TexturePtr gQuestIcons[14]; +//extern TexturePtr gBombersNotebookPhotos[24]; +} + +constexpr f32 fourByThree = 4.0f / 3.0f; + +// Gets the additional ratio of the screen compared to the original 4:3 ratio, clamping to 1 if smaller +extern "C" f32 Ship_GetExtendedAspectRatioMultiplier() { + f32 currentRatio = OTRGetAspectRatio(); + return MAX(currentRatio / fourByThree, 1.0f); +} + +// Enables Extended Culling options on specific actors by applying an inverse ratio of the draw distance slider +// to the projected Z value of the actor. This tricks distance checks without having to replace hardcoded values. +// Requires that Ship_ExtendedCullingActorRestoreProjectedPos is called within the same function scope. +extern "C" void Ship_ExtendedCullingActorAdjustProjectedZ(Actor* actor) { + s32 multiplier = CVarGetInteger("gEnhancements.Graphics.IncreaseActorDrawDistance", 1); + multiplier = MAX(multiplier, 1); + if (multiplier > 1) { + actor->projectedPos.z /= multiplier; + } +} + +// Enables Extended Culling options on specific actors by applying an inverse ratio of the widescreen aspect ratio +// to the projected X value of the actor. This tricks distance checks without having to replace hardcoded values. +// Requires that Ship_ExtendedCullingActorRestoreProjectedPos is called within the same function scope. +extern "C" void Ship_ExtendedCullingActorAdjustProjectedX(Actor* actor) { + if (CVarGetInteger("gEnhancements.Graphics.ActorCullingAccountsForWidescreen", 0)) { + f32 ratioAdjusted = Ship_GetExtendedAspectRatioMultiplier(); + actor->projectedPos.x /= ratioAdjusted; + } +} + +// Restores the projectedPos values on the actor after modifications from the Extended Culling hacks +//extern "C" void Ship_ExtendedCullingActorRestoreProjectedPos(PlayState* play, Actor* actor) { +// f32 invW = 0.0f; +// Actor_GetProjectedPos(play, &actor->world.pos, &actor->projectedPos, &invW); +//} + +extern "C" bool Ship_IsCStringEmpty(const char* str) { + return str == NULL || str[0] == '\0'; +} + +// Build vertex coordinates for a quad command +// In order of top left, top right, bottom left, then bottom right +// Supports flipping the texture horizontally +extern "C" void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH) { + vtxList[0].v.ob[0] = xStart; + vtxList[0].v.ob[1] = yStart; + vtxList[0].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[0].v.tc[1] = 0 << 5; + + vtxList[1].v.ob[0] = xStart + width; + vtxList[1].v.ob[1] = yStart; + vtxList[1].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[1].v.tc[1] = 0 << 5; + + vtxList[2].v.ob[0] = xStart; + vtxList[2].v.ob[1] = yStart + height; + vtxList[2].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[2].v.tc[1] = height << 5; + + vtxList[3].v.ob[0] = xStart + width; + vtxList[3].v.ob[1] = yStart + height; + vtxList[3].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[3].v.tc[1] = height << 5; +} + +extern "C" f32 Ship_GetCharFontWidthNES(u8 character) { + u8 adjustedChar = character - ' '; + + if (adjustedChar >= ARRAY_COUNTU(sNESFontWidths)) { + return 0.0f; + } + + return sNESFontWidths[adjustedChar]; +} + +//extern "C" TexturePtr Ship_GetCharFontTextureNES(u8 character) { +// u8 adjustedChar = character - ' '; +// +// if (adjustedChar >= ARRAY_COUNTU(sNESFontWidths)) { +// return (TexturePtr)gEmptyTexture; +// } +// +// return (TexturePtr)fontTbl[adjustedChar]; +//} + +//void LoadGuiTextures() { +// for (TexturePtr entry : gItemIcons) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +// for (TexturePtr entry : gQuestIcons) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +// for (TexturePtr entry : gBombersNotebookPhotos) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +//} diff --git a/soh/soh/ShipUtils.h b/soh/soh/ShipUtils.h new file mode 100644 index 00000000000..a1ac3081bdf --- /dev/null +++ b/soh/soh/ShipUtils.h @@ -0,0 +1,31 @@ +#ifndef SHIP_UTILS_H +#define SHIP_UTILS_H + +#include +//#include "PR/ultratypes.h" + +#ifdef __cplusplus + +void LoadGuiTextures(); + +extern "C" { +#endif + +struct PlayState; +struct Actor; + +f32 Ship_GetExtendedAspectRatioMultiplier(); +void Ship_ExtendedCullingActorAdjustProjectedZ(Actor* actor); +void Ship_ExtendedCullingActorAdjustProjectedX(Actor* actor); +void Ship_ExtendedCullingActorRestoreProjectedPos(PlayState* play, Actor* actor); + +bool Ship_IsCStringEmpty(const char* str); +void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH); +f32 Ship_GetCharFontWidthNES(u8 character); +//TexturePtr Ship_GetCharFontTextureNES(u8 character); + +#ifdef __cplusplus +} +#endif + +#endif // SHIP_UTILS_H diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp new file mode 100644 index 00000000000..eabad2953a5 --- /dev/null +++ b/soh/soh/SohGui/Menu.cpp @@ -0,0 +1,811 @@ +#include "Menu.h" +#include "UIWidgets.hpp" +#include "UIWidgets2.hpp" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/controls/SohInputEditorWindow.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h"/* +#include "DeveloperTools/SaveEditor.h" +#include "DeveloperTools/ActorViewer.h" +#include "DeveloperTools/CollisionViewer.h" +#include "DeveloperTools/EventLog.h" +#include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h" +#include "HudEditor.h" +#include "Notification.h"*/ +#include +#include +#include "variables.h" +#include +#include + +extern "C" { +#include "z64.h" +#include "functions.h" +extern PlayState* gPlayState; +} +std::vector windowTypeSizes = { {} }; + +extern std::unordered_map warpPointSceneList; +extern void Warp(); + +namespace SohGui {} + +namespace Ship { +std::string disabledTempTooltip; +const char* disabledTooltip; +bool disabledValue = false; + +bool operator==(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return l.r == r.r && l.g == r.g && l.b == r.b; +} + +bool operator==(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return l.r == r.r && l.g == r.g && l.b == r.b && l.a == r.a; +} + +bool operator<(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return (l.r < r.r && l.g <= r.g && l.b <= r.b) || (l.r <= r.r && l.g < r.g && l.b <= r.b) || + (l.r <= r.r && l.g <= r.g && l.b < r.b); +} + +bool operator<(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return (l.r < r.r && l.g <= r.g && l.b <= r.b && l.a <= r.a) || + (l.r <= r.r && l.g < r.g && l.b <= r.b && l.a <= r.a) || + (l.r <= r.r && l.g <= r.g && l.b < r.b && l.a <= r.a) || + (l.r <= r.r && l.g <= r.g && l.b <= r.b && l.a < r.a); +} + +bool operator>(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return (l.r > r.r && l.g >= r.g && l.b >= r.b) || (l.r >= r.r && l.g > r.g && l.b >= r.b) || + (l.r >= r.r && l.g >= r.g && l.b > r.b); +} + +bool operator>(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return (l.r > r.r && l.g >= r.g && l.b >= r.b && l.a >= r.a) || + (l.r >= r.r && l.g > r.g && l.b >= r.b && l.a >= r.a) || + (l.r >= r.r && l.g >= r.g && l.b > r.b && l.a >= r.a) || + (l.r >= r.r && l.g >= r.g && l.b >= r.b && l.a > r.a); +} + +uint32_t GetVectorIndexOf(std::vector& vector, std::string value) { + return std::distance(vector.begin(), std::find(vector.begin(), vector.end(), value)); +} + +void Menu::InsertSidebarSearch() { + menuEntries["Settings"].sidebars.emplace("Search", searchSidebarEntry); + uint32_t curIndex = 0; + if (!Ship_IsCStringEmpty(CVarGetString(menuEntries["Settings"].sidebarCvar, ""))) { + curIndex = GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, + CVarGetString(menuEntries["Settings"].sidebarCvar, "")); + } + menuEntries["Settings"].sidebarOrder.insert(menuEntries["Settings"].sidebarOrder.begin() + searchSidebarIndex, + "Search"); + if (curIndex > searchSidebarIndex) { + CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str()); + } +} + +void Menu::RemoveSidebarSearch() { + uint32_t curIndex = + GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, CVarGetString(menuEntries["Settings"].sidebarCvar, "")); + menuEntries["Settings"].sidebars.erase("Search"); + std::erase_if(menuEntries["Settings"].sidebarOrder, [](std::string& name) { return name == "Search"; }); + if (curIndex > searchSidebarIndex) { + curIndex--; + } else if (curIndex >= menuEntries["Settings"].sidebarOrder.size()) { + curIndex = menuEntries["Settings"].sidebarOrder.size() - 1; + } + CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str()); +} + +void Menu::UpdateWindowBackendObjects() { + Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend(); + int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1); + if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) { + configWindowBackend = static_cast(configWindowBackendId); + } else { + configWindowBackend = runningWindowBackend; + } + + availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends(); + for (auto& backend : *availableWindowBackends) { + availableWindowBackendsMap[backend] = windowBackendsMap.at(backend); + } +} + +Menu::Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_, + UIWidgets::Colors defaultThemeIndex_) + : GuiWindow(cVar, name), searchSidebarIndex(searchSidebarIndex_), defaultThemeIndex(defaultThemeIndex_) { +} + +void Menu::InitElement() { + popped = CVarGetInteger("gSettings.Menu.Popout", 0); + poppedSize.x = CVarGetInteger("gSettings.Menu.PoppedWidth", 1280); + poppedSize.y = CVarGetInteger("gSettings.Menu.PoppedHeight", 800); + poppedPos.x = CVarGetInteger("gSettings.Menu.PoppedPos.x", 0); + poppedPos.y = CVarGetInteger("gSettings.Menu.PoppedPos.y", 0); + + UpdateWindowBackendObjects(); +} + +void Menu::UpdateElement() { +} + +bool ModernMenuSidebarEntry(std::string label) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 pos = window->DC.CursorPos; + const ImGuiID sidebarId = window->GetID(std::string(label + "##Sidebar").c_str()); + ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + pos.y += style.FramePadding.y; + pos.x = window->WorkRect.GetCenter().x - labelSize.x / 2; + ImRect bb = { pos - style.FramePadding, pos + labelSize + style.FramePadding }; + ImGui::ItemSize(bb, style.FramePadding.y); + ImGui::ItemAdd(bb, sidebarId); + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, sidebarId, &hovered, &held); + if (pressed) { + ImGui::MarkItemEdited(sidebarId); + } + window->DrawList->AddRectFilled(pos - style.FramePadding, pos + labelSize + style.FramePadding, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button), + 3.0f); + UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + return pressed; +} + +bool ModernMenuHeaderEntry(std::string label) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 pos = window->DC.CursorPos; + const ImGuiID headerId = window->GetID(std::string(label + "##Header").c_str()); + ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + ImRect bb = { pos, pos + labelSize + style.FramePadding * 2 }; + ImGui::ItemSize(bb, style.FramePadding.y); + ImGui::ItemAdd(bb, headerId); + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, headerId, &hovered, &held); + window->DrawList->AddRectFilled(bb.Min, bb.Max, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button), + 3.0f); + pos += style.FramePadding; + UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + return pressed; +} + +uint32_t Menu::DrawSearchResults(std::string& menuSearchText) { + auto menuThemeIndex = static_cast(CVarGetInteger("gSettings.Menu.Theme", defaultThemeIndex)); + ImGui::BeginChild("Search Results"); + int searchCount = 0; + for (auto& menuLabel : menuOrder) { + auto& menuEntry = menuEntries.at(menuLabel); + for (auto& sidebarLabel : menuEntry.sidebarOrder) { + auto& sidebar = menuEntry.sidebars[sidebarLabel]; + int column = 1; + for (auto& info : sidebar.columnWidgets[column - 1]) { + if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || info.type == WIDGET_SEPARATOR_TEXT || + info.isHidden) { + continue; + } + const char* tooltip = info.options->tooltip; + std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : ""); + std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower); + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), + menuSearchText.end()); + std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower); + widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end()); + if (widgetStr.find(menuSearchText) != std::string::npos) { + MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray)); + std::string origin = fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, column); + ImGui::Text("%s", origin.c_str()); + ImGui::PopStyleColor(); + searchCount++; + } + } + column++; + } + } + return searchCount; +} + +void Menu::AddMenuEntry(std::string entryName, const char* entryCvar) { + menuEntries.emplace(entryName, MainMenuEntry{ entryName, entryCvar }); + menuOrder.push_back(entryName); +} + +std::unordered_map& Menu::GetDisabledMap() { + return disabledMap; +} + +void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex) { + disabledTempTooltip = "This setting is disabled because: \n\n"; + disabledValue = false; + disabledTooltip = " "; + + if (widget.preFunc != nullptr) { + widget.ResetDisables(); + widget.preFunc(widget); + if (widget.isHidden) { + return; + } + if (!widget.activeDisables.empty()) { + widget.options->disabled = true; + for (auto option : widget.activeDisables) { + disabledTempTooltip += std::string("- ") + disabledMap.at(option).reason + std::string("\n"); + } + widget.options->disabledTooltip = disabledTempTooltip.c_str(); + } + } + + if (widget.sameLine) { + ImGui::SameLine(); + } + + try { + switch (widget.type) { + case WIDGET_CHECKBOX: { + bool* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Checkbox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Checkbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), pointer, + *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CVAR_CHECKBOX: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarCheckbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), widget.cVar, + *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_AUDIO_BACKEND: { + auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetAudioBackend(); + UIWidgets::ComboboxOptions options = {}; + options.color = menuThemeIndex; + options.tooltip = "Sets the audio API used by the game. Requires a relaunch to take effect."; + options.disabled = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1; + options.disabledTooltip = "Only one audio API is available on this platform."; + if (UIWidgets::Combobox("Audio API", ¤tAudioBackend, audioBackendsMap, options)) { + Ship::Context::GetInstance()->GetAudio()->SetAudioBackend(currentAudioBackend); + } + } break; + case WIDGET_VIDEO_BACKEND: { + UIWidgets::ComboboxOptions options = {}; + options.color = menuThemeIndex; + options.tooltip = "Sets the renderer API used by the game."; + options.disabled = availableWindowBackends->size() <= 1; + options.disabledTooltip = "Only one renderer API is available on this platform."; + if (UIWidgets::Combobox("Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap, + options)) { + Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", + (int32_t)(configWindowBackend)); + Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name", + windowBackendsMap.at(configWindowBackend)); + Ship::Context::GetInstance()->GetConfig()->Save(); + UpdateWindowBackendObjects(); + } + } break; + case WIDGET_SEPARATOR: { + ImGui::Separator(); + } break; + case WIDGET_SEPARATOR_TEXT: { + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + } + ImGui::SeparatorText(widget.name.c_str()); + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PopStyleColor(); + } + } break; + case WIDGET_TEXT: { + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + } + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", widget.name.c_str()); + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PopStyleColor(); + } + } break; + case WIDGET_COMBOBOX: { + int32_t* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Combobox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Combobox(widget.name.c_str(), pointer, options->comboMap, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_CVAR_COMBOBOX: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarCombobox(widget.name.c_str(), widget.cVar, options->comboMap, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_SLIDER_INT: { + int32_t* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("int32 Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::SliderInt(widget.name.c_str(), pointer, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_CVAR_SLIDER_INT: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarSliderInt(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_SLIDER_FLOAT: { + float* pointer = std::get(widget.valuePointer); + + if (pointer == nullptr) { + SPDLOG_ERROR("float Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::SliderFloat(widget.name.c_str(), pointer, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CVAR_SLIDER_FLOAT: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarSliderFloat(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_BUTTON: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Button(widget.name.c_str(), *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CUSTOM: { + if (widget.customFunction != nullptr) { + widget.customFunction(widget); + } + } break; + case WIDGET_WINDOW_BUTTON: { + if (widget.windowName == nullptr || widget.windowName[0] == '\0') { + std::string msg = + fmt::format("Error drawing window contents for {}: windowName not defined", widget.name); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto window = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow(widget.windowName); + if (!window) { + std::string msg = + fmt::format("Error drawing window contents: windowName {} does not exist", widget.windowName); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + UIWidgets::WindowButton(widget.name.c_str(), widget.cVar, window, *options); + if (!window->IsVisible()) { + window->DrawElement(); + } + } break; + case WIDGET_SEARCH: { + UIWidgets::PushStyleButton(menuThemeIndex); + if (ImGui::Button("Clear")) { + menuSearch.Clear(); + } + ImGui::SameLine(); + if (CVarGetInteger("gSettings.Menu.SearchAutofocus", 0) && + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + UIWidgets::PushStyleCombobox(menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(menuThemeIndex)); + menuSearch.Draw(); + ImGui::PopStyleColor(); + UIWidgets::PopStyleCombobox(); + UIWidgets::PopStyleButton(); + std::string menuSearchText(menuSearch.InputBuf); + + if (menuSearchText == "") { + ImGui::Text("Start typing to see results."); + return; + } + DrawSearchResults(menuSearchText); + ImGui::EndChild(); + } break; + default: + break; + } + if (widget.postFunc != nullptr) { + widget.postFunc(widget); + } + } catch (const std::bad_variant_access& e) { + SPDLOG_ERROR("Failed to draw menu item \"{}\" due to: {}", widget.name, e.what()); + assert(false); + } +} + +void Menu::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +void Menu::DrawElement() { + for (auto& [reason, info] : disabledMap) { + info.active = info.evaluation(info); + } + auto menuThemeIndex = static_cast(CVarGetInteger("gSettings.Menu.Theme", defaultThemeIndex)); + + windowHeight = ImGui::GetMainViewport()->WorkSize.y; + windowWidth = ImGui::GetMainViewport()->WorkSize.x; + auto windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + bool popout = CVarGetInteger("gSettings.Menu.Popout", 0) && allowPopout; + if (popout) { + windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking; + } + if (popout != popped) { + if (popout) { + windowHeight = poppedSize.y; + windowWidth = poppedSize.x; + ImGui::SetNextWindowSize({ static_cast(windowWidth), static_cast(windowHeight) }, + ImGuiCond_Always); + ImGui::SetNextWindowPos(poppedPos, ImGuiCond_Always); + } else if (popped) { + CVarSetFloat("gSettings.Menu.PoppedWidth", poppedSize.x); + CVarSetFloat("gSettings.Menu.PoppedHeight", poppedSize.y); + CVarSave(); + } + } + popped = popout; + auto windowCond = ImGuiCond_Always; + if (!popout) { + ImGui::SetNextWindowSize({ static_cast(windowWidth), static_cast(windowHeight) }, windowCond); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), windowCond, { 0.5f, 0.5f }); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + } + if (!ImGui::Begin("Main Menu", NULL, windowFlags)) { + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::End(); + return; + } + if (popped != popout) { + if (!popout) { + ImGui::PopStyleVar(); + } + CVarSetInteger("gSettings.Menu.Popout", popped); + CVarSetFloat("gSettings.Menu.PoppedWidth", poppedSize.x); + CVarSetFloat("gSettings.Menu.PoppedHeight", poppedSize.y); + CVarSetFloat("gSettings.Menu.PoppedPos.x", poppedSize.x); + CVarSetFloat("gSettings.Menu.PoppedPos.y", poppedSize.y); + CVarSave(); + ImGui::End(); + return; + } + ImGui::PushFont(OTRGlobals::Instance->fontStandardLargest); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + windowHeight = window->WorkRect.GetHeight(); + windowWidth = window->WorkRect.GetWidth(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + const char* headerCvar = "gSettings.Menu.ActiveHeader"; + std::string headerIndex = CVarGetString(headerCvar, "Settings"); + ImVec2 pos = window->DC.CursorPos; + float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (menuEntries.size() + 1)); + std::vector headerSizes; + float headerWidth = style.ItemSpacing.x; + bool headerSearch = !CVarGetInteger("gSettings.Menu.SidebarSearch", 0); + if (headerSearch) { + headerWidth += 200.0f + style.ItemSpacing.x + style.FramePadding.x; + } + for (auto& label : menuOrder) { + ImVec2 size = ImGui::CalcTextSize(label.c_str()); + headerSizes.push_back(size); + headerWidth += size.x + style.FramePadding.x * 2; + if (label == headerIndex) { + headerWidth += style.ItemSpacing.x; + } + } + ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) }; + pos += window->WorkRect.GetSize() / 2 - menuSize / 2; + ImGui::SetNextWindowPos(pos); + ImGui::BeginChild("Menu Block", menuSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar); + + std::unordered_map* sidebar; + float headerHeight = headerSizes.at(0).y + style.FramePadding.y * 2; + ImVec2 buttonSize = ImGui::CalcTextSize(ICON_FA_TIMES_CIRCLE) + style.FramePadding * 2; + bool scrollbar = false; + if (headerWidth > menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3) { + headerHeight += style.ScrollbarSize; + scrollbar = true; + } + UIWidgets::ButtonOptions options = {}; + options.size = UIWidgets::Sizes::Inline; + options.tooltip = "Close Menu (Esc)"; + if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) { + ToggleVisibility(); + + // Update gamepad navigation after close based on if other menus are still visible + auto mImGuiIo = &ImGui::GetIO(); + if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) && + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) { + mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + } else { + mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; + } + } + ImGui::SameLine(); + ImGui::SetNextWindowSizeConstraints({ 0, headerHeight }, { headerWidth, headerHeight }); + ImVec2 headerSelSize = { menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3, headerHeight }; + if (scrollbar) { + headerSelSize.y += style.ScrollbarSize; + } + bool autoFocus = CVarGetInteger("gSettings.Menu.SearchAutofocus", 0); + ImGui::BeginChild("Header Selection", headerSelSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar); + uint8_t curIndex = 0; + for (auto& label : menuOrder) { + if (curIndex != 0) { + ImGui::SameLine(); + } + auto& entry = menuEntries.at(label); + std::string nextIndex = label; + UIWidgets::PushStyleButton(menuThemeIndex); + if (headerIndex != label) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuHeaderEntry(entry.label)) { + if (headerSearch) { + menuSearch.Clear(); + } + CVarSetString(headerCvar, label.c_str()); + CVarSave(); + nextIndex = label; + } + if (headerIndex != label) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (headerIndex == label) { + sidebar = &entry.sidebars; + } + if (nextIndex != label) { + headerIndex = nextIndex; + } + curIndex++; + } + std::string menuSearchText = ""; + if (headerSearch) { + ImGui::SameLine(); + if (autoFocus && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + auto color = UIWidgets::ColorValues.at(menuThemeIndex); + color.w = 0.2f; + ImGui::PushStyleColor(ImGuiCol_FrameBg, color); + menuSearch.Draw("##search", 200.0f); + menuSearchText = menuSearch.InputBuf; + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), menuSearchText.end()); + if (menuSearchText.length() < 1) { + ImGui::SameLine(headerWidth - 200.0f + style.ItemSpacing.x); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search..."); + } + ImGui::PopStyleColor(); + } + ImGui::EndChild(); + ImGui::SameLine(menuSize.x - (buttonSize.x * 2) - style.ItemSpacing.x); + UIWidgets::ButtonOptions options2 = {}; + options2.color = UIWidgets::Colors::Red; + options2.size = UIWidgets::Sizes::Inline; + options2.tooltip = "Reset" +#ifdef __APPLE__ + " (Command-R)" +#elif !defined(__SWITCH__) && !defined(__WIIU__) + " (Ctrl+R)" +#else + "" +#endif + ; + if (UIWidgets::Button(ICON_FA_UNDO, options2)) { + std::reinterpret_pointer_cast( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")) + ->Dispatch("reset"); + } + ImGui::SameLine(); + UIWidgets::ButtonOptions options3 = {}; + options3.color = UIWidgets::Colors::Red; + options3.size = UIWidgets::Sizes::Inline; + options3.tooltip = "Quit 2S2H"; + if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) { + if (!popped) { + ToggleVisibility(); + } + Ship::Context::GetInstance()->GetWindow()->Close(); + } + ImGui::PopStyleVar(); + + pos.y += headerHeight + style.ItemSpacing.y; + pos.x = centerX - menuSize.x / 2 + (style.ItemSpacing.x * (menuEntries.size() + 1)); + window->DrawList->AddRectFilled(pos, pos + ImVec2{ menuSize.x, 4 }, ImGui::GetColorU32({ 255, 255, 255, 255 }), + true, style.WindowRounding); + pos.y += style.ItemSpacing.y; + float sectionHeight = menuSize.y - headerHeight - 4 - style.ItemSpacing.y * 2; + float columnHeight = sectionHeight - style.ItemSpacing.y * 4; + ImGui::SetNextWindowPos(pos + style.ItemSpacing * 2); + float sidebarWidth = 200 - style.ItemSpacing.x; + + const char* sidebarCvar = menuEntries.at(headerIndex).sidebarCvar; + + std::string sectionIndex = CVarGetString(sidebarCvar, ""); + if (!sidebar->contains(sectionIndex)) { + sectionIndex = sidebar->begin()->first; + } + float sectionCenterX = pos.x + (sidebarWidth / 2); + float topY = pos.y; + ImGui::SetNextWindowSizeConstraints({ sidebarWidth, 0 }, { sidebarWidth, columnHeight }); + ImGui::BeginChild((menuEntries.at(headerIndex).label + " Section").c_str(), { sidebarWidth, columnHeight * 3 }, + ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, ImGuiWindowFlags_NoTitleBar); + for (auto& sidebarLabel : menuEntries.at(headerIndex).sidebarOrder) { + std::string nextIndex = ""; + UIWidgets::PushStyleButton(menuThemeIndex); + if (sectionIndex != sidebarLabel) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuSidebarEntry(sidebarLabel)) { + if (headerSearch) { + menuSearch.Clear(); + } + CVarSetString(sidebarCvar, sidebarLabel.c_str()); + CVarSave(); + nextIndex = sidebarLabel; + } + if (sectionIndex != sidebarLabel) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (nextIndex != "") { + sectionIndex = nextIndex; + } + } + ImGui::EndChild(); + + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + pos = ImVec2{ sectionCenterX + (sidebarWidth / 2), topY } + style.ItemSpacing * 2; + window->DrawList->AddRectFilled(pos, pos + ImVec2{ 4, sectionHeight - style.FramePadding.y * 2 }, + ImGui::GetColorU32({ 255, 255, 255, 255 }), true, style.WindowRounding); + pos.x += 4 + style.ItemSpacing.x; + ImGui::SetNextWindowPos(pos + style.ItemSpacing); + float sectionWidth = menuSize.x - sidebarWidth - 4 - style.ItemSpacing.x * 4; + std::string sectionMenuId = sectionIndex + " Settings"; + int columns = sidebar->at(sectionIndex).columnCount; + size_t columnFuncs = sidebar->at(sectionIndex).columnWidgets.size(); + if (windowWidth < 800) { + columns = 1; + } + float columnWidth = (sectionWidth - style.ItemSpacing.x * columns) / columns; + bool useColumns = columns > 1; + if (!useColumns || (headerSearch && menuSearchText.length() > 0)) { + ImGui::SameLine(); + ImGui::SetNextWindowSizeConstraints({ sectionWidth, 0 }, { sectionWidth, columnHeight }); + ImGui::BeginChild(sectionMenuId.c_str(), { sectionWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY, + ImGuiWindowFlags_NoTitleBar); + } + if (headerSearch && menuSearchText.length() > 0) { + uint32_t searchCount = DrawSearchResults(menuSearchText); + if (searchCount == 0) { + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("No results found").x) / 2); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "No results found"); + } + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("Clear Search").x) / 2 - 10.0f); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + UIWidgets::ButtonOptions clearBtnOpts = {}; + clearBtnOpts.size = UIWidgets::Sizes::Inline; + if (UIWidgets::Button("Clear Search", clearBtnOpts)) { + menuSearch.Clear(); + } + + ImGui::EndChild(); + } else { + std::string menuLabel = menuEntries.at(headerIndex).label; + if (MenuInit::GetUpdateFuncs().contains(menuLabel)) { + if (MenuInit::GetUpdateFuncs()[menuLabel].contains(sectionIndex)) { + for (auto& updateFunc : MenuInit::GetUpdateFuncs()[menuLabel][sectionIndex]) { + updateFunc(); + } + } + } + for (int i = 0; i < columnFuncs; i++) { + std::string sectionId = fmt::format("{} Column {}", sectionMenuId, i); + if (useColumns) { + ImGui::SetNextWindowSizeConstraints({ columnWidth, 0 }, { columnWidth, columnHeight }); + ImGui::BeginChild(sectionId.c_str(), { columnWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY, + ImGuiWindowFlags_NoTitleBar); + } + // for (auto& entryName : sidebar->at(sectionIndex).sidebarOrder) { + for (auto& entry : sidebar->at(sectionIndex).columnWidgets.at(i)) { + MenuDrawItem(entry, 90 / sidebar->at(sectionIndex).columnCount, menuThemeIndex); + } + //} + if (useColumns) { + ImGui::EndChild(); + } + if (i < columns - 1) { + ImGui::SameLine(); + } + } + } + if (!useColumns || menuSearchText.length() > 0) { + ImGui::EndChild(); + } + ImGui::PopFont(); + ImGui::PopFont(); + + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + if (popout) { + poppedSize = ImGui::GetWindowSize(); + poppedPos = ImGui::GetWindowPos(); + } + ImGui::End(); +} +} // namespace Ship diff --git a/soh/soh/SohGui/Menu.h b/soh/soh/SohGui/Menu.h new file mode 100644 index 00000000000..dc91d8fa5da --- /dev/null +++ b/soh/soh/SohGui/Menu.h @@ -0,0 +1,65 @@ +#ifndef MENU_H +#define MENU_H + +#include +#include "UIWidgets.hpp" +#include "UIWidgets2.hpp" +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "MenuTypes.h" + +namespace Ship { +uint32_t GetVectorIndexOf(std::vector& vector, std::string value); +class Menu : public GuiWindow { + public: + using GuiWindow::GuiWindow; + + Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_ = 0, + UIWidgets::Colors menuThemeIndex_ = UIWidgets::Colors::LightBlue); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + void InsertSidebarSearch(); + void RemoveSidebarSearch(); + void UpdateWindowBackendObjects(); + + void MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex); + void AddMenuEntry(std::string entryName, const char* entryCvar); + std::unordered_map& GetDisabledMap(); + + protected: + ImVec2 mOriginalSize; + std::string mName; + uint32_t mWindowFlags; + std::unordered_map menuEntries; + std::vector menuOrder; + uint32_t DrawSearchResults(std::string& menuSearchText); + ImGuiTextFilter menuSearch; + uint8_t searchSidebarIndex; + UIWidgets::Colors defaultThemeIndex; + std::shared_ptr> availableWindowBackends; + std::unordered_map availableWindowBackendsMap; + Ship::WindowBackend configWindowBackend; + + std::unordered_map disabledMap; + std::vector disabledVector; + const SidebarEntry searchSidebarEntry = { + .columnCount = 1, + .columnWidgets = { { { .name = "Sidebar Search", + .type = WIDGET_SEARCH, + .options = std::make_shared(UIWidgets::WidgetOptions{}.Tooltip( + "Searches all menus for the given text, including tooltips.")) } } } + }; + + private: + bool allowPopout = true; // PortNote: should be set to false on small screen ports + bool popped; + ImVec2 poppedSize; + ImVec2 poppedPos; + float windowHeight; + float windowWidth; +}; +} // namespace Ship + +#endif // MENU_H diff --git a/soh/soh/SohGui/MenuTypes.h b/soh/soh/SohGui/MenuTypes.h new file mode 100644 index 00000000000..00e6400acf6 --- /dev/null +++ b/soh/soh/SohGui/MenuTypes.h @@ -0,0 +1,301 @@ +#ifndef MENUTYPES_H +#define MENUTYPES_H + +#include +#include "UIWidgets.hpp" +#include "UIWidgets2.hpp" + +typedef enum { + DISABLE_FOR_GYRO_OFF, + DISABLE_FOR_GYRO_ON, + DISABLE_FOR_RIGHT_STICK_OFF, + DISABLE_FOR_AUTO_SAVE_OFF, + DISABLE_FOR_NULL_PLAY_STATE, + DISABLE_FOR_DEBUG_MODE_OFF, + DISABLE_FOR_NO_VSYNC, + DISABLE_FOR_NO_WINDOWED_FULLSCREEN, + DISABLE_FOR_NO_MULTI_VIEWPORT, + DISABLE_FOR_NOT_DIRECTX, + DISABLE_FOR_DIRECTX, + DISABLE_FOR_MATCH_REFRESH_RATE_ON, + DISABLE_FOR_FRAME_ADVANCE_OFF, + DISABLE_FOR_INTRO_SKIP_OFF, + DISABLE_FOR_ADVANCED_RESOLUTION_ON, + DISABLE_FOR_VERTICAL_RES_TOGGLE_ON, + DISABLE_FOR_LOW_RES_MODE_ON, +} DisableOption; + +struct WidgetInfo; +struct disabledInfo; +using VoidFunc = void (*)(); +using DisableInfoFunc = bool (*)(disabledInfo&); +using DisableVec = std::vector; +using WidgetFunc = void (*)(WidgetInfo&); + +typedef enum { + WIDGET_CHECKBOX, + WIDGET_COMBOBOX, + WIDGET_SLIDER_INT, + WIDGET_SLIDER_FLOAT, + WIDGET_CVAR_CHECKBOX, + WIDGET_CVAR_COMBOBOX, + WIDGET_CVAR_SLIDER_INT, + WIDGET_CVAR_SLIDER_FLOAT, + WIDGET_BUTTON, + WIDGET_COLOR_24, // color picker without alpha + WIDGET_COLOR_32, // color picker with alpha + WIDGET_SEARCH, + WIDGET_SEPARATOR, + WIDGET_SEPARATOR_TEXT, + WIDGET_TEXT, + WIDGET_WINDOW_BUTTON, + WIDGET_AUDIO_BACKEND, // needed for special operations that can't be handled easily with the normal combobox widget + WIDGET_VIDEO_BACKEND, // same as above + WIDGET_CUSTOM, +} WidgetType; + +typedef enum { + SECTION_COLUMN_1, + SECTION_COLUMN_2, + SECTION_COLUMN_3, +} SectionColumns; + +typedef enum { + DEBUG_LOG_TRACE, + DEBUG_LOG_DEBUG, + DEBUG_LOG_INFO, + DEBUG_LOG_WARN, + DEBUG_LOG_ERROR, + DEBUG_LOG_CRITICAL, + DEBUG_LOG_OFF, +} DebugLogOption; + +// holds the widget values for a widget, contains all CVar types available from LUS. int32_t is used for boolean +// evaluation +using CVarVariant = std::variant; +using OptionsVariant = + std::variant; + +// All the info needed for display and search of all widgets in the menu. +// `name` is the label displayed, +// `cVar` is the string representation of the CVar used to store the widget value +// `tooltip` is what is displayed when hovering (except when disabled, more on that later) +// `type` is the WidgetType for the widget, which is what determines how the information is used in the draw func +// `options` is a variant that holds the UIWidgetsOptions struct for the widget type +// blank objects need to be initialized with specific typing matching the expected Options struct for the widget +// `callback` is a lambda used for running code on widget change. may need `BenGui::GetMenu()` for specific menu actions +// `preFunc` is a lambda called before drawing code starts. It can be used to determine a widget's status, +// whether disabled or hidden, as well as update pointers for non-CVar widget types. +// `postFunc` is a lambda called after all drawing code is finished, for reacting to states other than +// widgets having been changed, like holding buttons. +// All three lambdas accept a `widgetInfo` reference in case it needs information on the widget for these operations +// `activeDisables` is a vector of DisableOptions for specifying what reasons a widget is disabled, which are displayed +// in the disabledTooltip for the widget. Can display multiple reasons. Handling the reasons is done in `preFunc`. +// It is recommended to utilize `disabledInfo`/`DisableReason` to list out all reasons for disabling and isHidden so +// the info can be shown. +// `windowName` is what is displayed and searched for `windowButton` type and window interactions +// `isHidden` just prevents the widget from being drawn under whatever circumstances you specify in the `preFunc` +// `sameLine` allows for specifying that the widget should be on the same line as the previous widget +struct WidgetInfo { + std::string name; // Used by all widgets + const char* cVar; // Used by all widgets except + WidgetType type; + std::shared_ptr options; + std::variant valuePointer; + WidgetFunc callback = nullptr; + WidgetFunc preFunc = nullptr; + WidgetFunc postFunc = nullptr; + WidgetFunc customFunction = nullptr; + DisableVec activeDisables = {}; + const char* windowName = ""; + bool isHidden = false; + bool sameLine = false; + + WidgetInfo& CVar(const char* cVar_) { + cVar = cVar_; + return *this; + } + WidgetInfo& Options(OptionsVariant options_) { + switch (type) { + case WIDGET_AUDIO_BACKEND: + case WIDGET_VIDEO_BACKEND: + case WIDGET_COMBOBOX: + case WIDGET_CVAR_COMBOBOX: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_CHECKBOX: + case WIDGET_CVAR_CHECKBOX: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_SLIDER_FLOAT: + case WIDGET_CVAR_SLIDER_FLOAT: + options = + std::make_shared(std::get(options_)); + break; + case WIDGET_SLIDER_INT: + case WIDGET_CVAR_SLIDER_INT: + options = + std::make_shared(std::get(options_)); + break; + case WIDGET_BUTTON: + case WIDGET_WINDOW_BUTTON: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_TEXT: + case WIDGET_SEPARATOR_TEXT: + case WIDGET_SEPARATOR: + default: + options = std::make_shared(std::get(options_)); + } + return *this; + } + void ResetDisables() { + isHidden = false; + options->disabled = false; + options->disabledTooltip = ""; + activeDisables.clear(); + } + WidgetInfo& Options(std::shared_ptr options_) { + options = options_; + return *this; + } + WidgetInfo& Callback(WidgetFunc callback_) { + callback = callback_; + return *this; + } + WidgetInfo& PreFunc(WidgetFunc preFunc_) { + preFunc = preFunc_; + return *this; + } + WidgetInfo& PostFunc(WidgetFunc postFunc_) { + postFunc = postFunc_; + return *this; + } + WidgetInfo& WindowName(const char* windowName_) { + windowName = windowName_; + return *this; + } + WidgetInfo& ValuePointer(std::variant valuePointer_) { + valuePointer = valuePointer_; + return *this; + } + WidgetInfo& SameLine(bool sameLine_) { + sameLine = sameLine_; + return *this; + } + WidgetInfo& CustomFunction(WidgetFunc customFunction_) { + customFunction = customFunction_; + return *this; + } +}; + +struct WidgetPath { + std::string sectionName; + std::string sidebarName; + SectionColumns column; +}; + +// `disabledInfo` holds information on reasons for hiding or disabling a widget, as well as an evaluation lambda that +// is run once per frame to update its status (this is done to prevent dozens of redundant CVarGets in each frame loop) +// `evaluation` returns a bool which can be determined by whatever code you want that changes its status +// `reason` is the text displayed in the disabledTooltip when a widget is disabled by a particular DisableReason +// `active` is what's referenced when determining disabled status for a widget that uses this This can also be used to +// hold reasons to hide widgets so that their evaluations are also only run once per frame +struct disabledInfo { + DisableInfoFunc evaluation; + const char* reason; + bool active = false; + int32_t value = 0; +}; + +// struct Sidebar { +// //std::unordered_map entries; +// uint32_t columnCount; +// std::vector> columnWidgets; +// +// void Insert(std::string entryName, WidgetInfo& entry, int32_t index = -1) { +// if (index == -1 || index >= entryOrder.size()) { +// entryOrder.push_back(entryName); +// } else { +// entryOrder.insert(entryOrder.begin() + index, entryName); +// } +// columnWidgets[entryName].push_back({entry}); +// } +// +// void Erase(std::string entryName) { +// if (columnWidgets.contains(entryName)) { +// columnWidgets.erase(entryName); +// } +// std::erase_if(entryOrder, [entryName](std::string name) { return name == entryName; }); +// } +// }; + +// Contains the name displayed in the sidebar (label), the number of columns to use in drawing (columnCount; for visual +// separation, 1-3), and nested vectors of the widgets, grouped by column (columnWidgets). The number of widget vectors +// added to the column groups does not need to match the specified columnCount, e.g. you can have one vector added to +// the sidebar, but still separate the window into 3 columns and display only in the first column +struct SidebarEntry { + uint32_t columnCount; + std::vector> columnWidgets; +}; + +// Contains entries for what's listed in the header at the top, including the name displayed on the top bar (label), +// a vector of the SidebarEntries for that header entry, and the name of the cvar used to track what sidebar entry is +// the last viewed for that header. +struct MainMenuEntry { + std::string label; + const char* sidebarCvar; + std::unordered_map sidebars = {}; + std::vector sidebarOrder = {}; +}; + +static const std::unordered_map audioBackendsMap = { + { Ship::AudioBackend::WASAPI, "Windows Audio Session API" }, + { Ship::AudioBackend::SDL, "SDL" }, +}; + +static const std::unordered_map windowBackendsMap = { + { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" }, + { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" }, + { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" }, +}; + +struct MenuInit { + static std::vector>& GetInitFuncs() { + static std::vector> menuInitFuncs; + return menuInitFuncs; + } + + static std::unordered_map>>>& + GetUpdateFuncs() { + static std::unordered_map>>> + menuUpdateFuncs; + return menuUpdateFuncs; + } + + static void InitAll() { + auto& menuInitFuncs = MenuInit::GetInitFuncs(); + for (const auto& initFunc : menuInitFuncs) { + initFunc(); + } + } +}; + +struct RegisterMenuInitFunc { + RegisterMenuInitFunc(std::function initFunc) { + auto& menuInitFuncs = MenuInit::GetInitFuncs(); + + menuInitFuncs.push_back(initFunc); + } +}; + +struct RegisterMenuUpdateFunc { + RegisterMenuUpdateFunc(std::function updateFunc, std::string sectionName, std::string sidebarName) { + auto& menuUpdateFuncs = MenuInit::GetUpdateFuncs(); + + menuUpdateFuncs[sectionName][sidebarName].push_back(updateFunc); + } +}; + +#endif // MENUTYPES_H diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 84a44b0525a..c88c90d353d 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -22,6 +22,7 @@ #endif #include "UIWidgets.hpp" +#include "SohMenu.h" #include "include/global.h" #include "include/z64audio.h" #include "soh/SaveManager.h" @@ -108,7 +109,8 @@ namespace SohGui { std::shared_ptr mStatsWindow; std::shared_ptr mGfxDebuggerWindow; std::shared_ptr mInputEditorWindow; - + + std::shared_ptr mSohMenu; std::shared_ptr mAudioEditorWindow; std::shared_ptr mInputViewer; std::shared_ptr mInputViewerSettings; @@ -142,14 +144,17 @@ namespace SohGui { mSohMenuBar = std::make_shared(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0)); gui->SetMenuBar(std::reinterpret_pointer_cast(mSohMenuBar)); - if (gui->GetMenuBar() && !gui->GetMenuBar()->IsVisible()) { + if (!gui->GetMenuBar() && !CVarGetInteger("gSettings.DisableMenuShortcutNotify", 0)) { #if defined(__SWITCH__) || defined(__WIIU__) - Notification::Emit({ .message = "Press - to access enhancements menu", .remainingTime = 10.0f }); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press - to access enhancements menu"); #else - Notification::Emit({ .message = "Press F1 to access enhancements menu", .remainingTime = 10.0f }); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press F1 to access enhancements menu"); #endif } + mSohMenu = std::make_shared(CVAR_WINDOW("Menu"), "Port Menu"); + gui->SetMenu(mSohMenu); + mStatsWindow = gui->GetGuiWindow("Stats"); if (mStatsWindow == nullptr) { SPDLOG_ERROR("Could not find stats window"); diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp new file mode 100644 index 00000000000..ff5d09ac91b --- /dev/null +++ b/soh/soh/SohGui/SohMenu.cpp @@ -0,0 +1,578 @@ +#include "SohMenu.h" +#include "UIWidgets2.hpp" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/controls/SohInputEditorWindow.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" +#include "soh/Notification/Notification.h" +#include +#include "StringHelper.h" +#include +#include "variables.h" +#include +#include +//#include "ResolutionEditor.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +extern PlayState* gPlayState; +} +extern std::unordered_map warpPointSceneList; +extern void Warp(); + +namespace SohGui { +extern std::shared_ptr mBenMenu; +void FreeLookPitchMinMax() { + f32 maxY = CVarGetFloat("gEnhancements.Camera.FreeLook.MaxPitch", 72.0f); + f32 minY = CVarGetFloat("gEnhancements.Camera.FreeLook.MinPitch", -49.0f); + CVarSetFloat("gEnhancements.Camera.FreeLook.MaxPitch", std::max(maxY, minY)); + CVarSetFloat("gEnhancements.Camera.FreeLook.MinPitch", std::min(maxY, minY)); +} + +using namespace UIWidgets; + +void SohMenu::AddSidebarEntry(std::string sectionName, std::string sidebarName, uint32_t columnCount) { + assert(!sectionName.empty()); + assert(!sidebarName.empty()); + menuEntries.at(sectionName).sidebars.emplace(sidebarName, SidebarEntry{ .columnCount = columnCount }); + menuEntries.at(sectionName).sidebarOrder.push_back(sidebarName); +} + +WidgetInfo& SohMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType) { + assert(!widgetName.empty()); // Must be unique + assert(menuEntries.contains(pathInfo.sectionName)); // Section/header must already exist + assert(menuEntries.at(pathInfo.sectionName).sidebars.contains(pathInfo.sidebarName)); // Sidebar must already exist + std::unordered_map& sidebar = menuEntries.at(pathInfo.sectionName).sidebars; + uint8_t column = pathInfo.column; + if (sidebar.contains(pathInfo.sidebarName)) { + while (sidebar.at(pathInfo.sidebarName).columnWidgets.size() < column + 1) { + sidebar.at(pathInfo.sidebarName).columnWidgets.push_back({}); + } + } + SidebarEntry& entry = sidebar.at(pathInfo.sidebarName); + entry.columnWidgets.at(column).push_back({ .name = widgetName, .type = widgetType }); + WidgetInfo& widget = entry.columnWidgets.at(column).back(); + switch (widgetType) { + case WIDGET_CHECKBOX: + case WIDGET_CVAR_CHECKBOX: + widget.options = std::make_shared(); + break; + case WIDGET_SLIDER_FLOAT: + case WIDGET_CVAR_SLIDER_FLOAT: + widget.options = std::make_shared(); + break; + case WIDGET_SLIDER_INT: + case WIDGET_CVAR_SLIDER_INT: + widget.options = std::make_shared(); + break; + case WIDGET_COMBOBOX: + case WIDGET_CVAR_COMBOBOX: + case WIDGET_AUDIO_BACKEND: + case WIDGET_VIDEO_BACKEND: + widget.options = std::make_shared(); + break; + case WIDGET_BUTTON: + widget.options = std::make_shared(); + break; + case WIDGET_WINDOW_BUTTON: + widget.options = std::make_shared(ButtonOptions{ .size = Sizes::Inline }); + break; + case WIDGET_COLOR_24: + case WIDGET_COLOR_32: + break; + case WIDGET_SEARCH: + case WIDGET_SEPARATOR: + case WIDGET_SEPARATOR_TEXT: + case WIDGET_TEXT: + default: + widget.options = std::make_shared(); + } + return widget; +} + +void SohMenu::AddSettings() { + // Add Settings menu + AddMenuEntry("Settings", "gSettings.Menu.SettingsSidebarSection"); + // General Settings + AddSidebarEntry("Settings", "General", 3); + WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 }; + AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX) + .CVar("gSettings.Menu.Theme") + .Options(ComboboxOptions() + .Tooltip("Changes the Theme of the Menu Widgets.") + .ComboMap(menuThemeOptions) + .DefaultIndex(Colors::LightBlue)); +#if not defined(__SWITCH__) and not defined(__WIIU__) + AddWidget(path, "Menu Controller Navigation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_IMGUI_CONTROLLER_NAV) + .Options(CheckboxOptions().Tooltip( + "Allows controller navigation of the 2Ship menu (Settings, Enhancements,...)\nCAUTION: " + "This will disable game inputs while the menu is visible.\n\nD-pad to move between " + "items, A to select, B to move up in scope.")); + AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.CursorVisibility") + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetForceCursorVisibility( + CVarGetInteger("gSettings.CursorVisibility", 0)); + }) + .Options(CheckboxOptions().Tooltip("Makes the cursor always visible, even in full screen.")); +#endif + AddWidget(path, "Search In Sidebar", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.Menu.SidebarSearch") + .Callback([](WidgetInfo& info) { + if (CVarGetInteger("gSettings.Menu.SidebarSearch", 0)) { + mBenMenu->InsertSidebarSearch(); + } else { + mBenMenu->RemoveSidebarSearch(); + } + }) + .Options(CheckboxOptions().Tooltip( + "Displays the Search menu as a sidebar entry in Settings instead of in the header.")); + AddWidget(path, "Search Input Autofocus", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.Menu.SearchAutofocus") + .Options(CheckboxOptions().Tooltip( + "Search input box gets autofocus when visible. Does not affect using other widgets.")); + AddWidget(path, "Alt Assets Tab hotkey", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.Mods.AlternateAssetsHotkey") + .Options( + CheckboxOptions().Tooltip("Allows pressing the Tab key to toggle alternate assets").DefaultValue(true)); + AddWidget(path, "Open App Files Folder", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath(); + SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); + }) + .Options(ButtonOptions().Tooltip("Opens the folder that contains the save and mods folders, etc.")); + + // Audio Settings + path.sidebarName = "Audio"; + AddSidebarEntry("Settings", "Audio", 3); + AddWidget(path, "Master Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gSettings.Audio.MasterVolume") + .Options(FloatSliderOptions() + .Tooltip("Adjust the overall sound volume.") + .ShowButtons(false) + .Format("") + .IsPercentage()); + /*AddWidget(path, "Main Music Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gSettings.Audio.MainMusicVolume") + .Callback([](WidgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_BGM_MAIN, CVarGetFloat("gSettings.Audio.MainMusicVolume", 1.0f)); + }) + .Options(FloatSliderOptions() + .Tooltip("Adjust the background music volume.") + .ShowButtons(false) + .Format("") + .IsPercentage()); + AddWidget(path, "Sub Music Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gSettings.Audio.SubMusicVolume") + .Callback([](WidgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_BGM_SUB, CVarGetFloat("gSettings.Audio.SubMusicVolume", 1.0f)); + }) + .Options( + FloatSliderOptions().Tooltip("Adjust the sub music volume.").ShowButtons(false).Format("").IsPercentage()); + AddWidget(path, "Sound Effects Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gSettings.Audio.SoundEffectsVolume") + .Callback([](WidgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_SFX, CVarGetFloat("gSettings.Audio.SoundEffectsVolume", 1.0f)); + }) + .Options(FloatSliderOptions() + .Tooltip("Adjust the sound effects volume.") + .ShowButtons(false) + .Format("") + .IsPercentage()); + AddWidget(path, "Fanfare Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gSettings.Audio.FanfareVolume") + .Callback([](WidgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_FANFARE, CVarGetFloat("gSettings.Audio.FanfareVolume", 1.0f)); + }) + .Options( + FloatSliderOptions().Tooltip("Adjust the fanfare volume.").ShowButtons(false).Format("").IsPercentage()); + AddWidget(path, "Ambience Volume: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gSettings.Audio.AmbienceVolume") + .Callback([](WidgetInfo& info) { + AudioSeq_SetPortVolumeScale(SEQ_PLAYER_AMBIENCE, CVarGetFloat("gSettings.Audio.AmbienceVolume", 1.0f)); + }) + .Options(FloatSliderOptions() + .Tooltip("Adjust the ambient sound volume.") + .ShowButtons(false) + .Format("") + .IsPercentage());*/ + AddWidget(path, "Audio API", WIDGET_AUDIO_BACKEND); + + // Graphics Settings + static int32_t maxFps; + const char* tooltip = ""; + if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { + maxFps = 360; + tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc.\n\nA higher target " + "FPS than your monitor's refresh rate will waste resources, and might give a worse result."; + } else { + maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc."; + } + path.sidebarName = "Graphics"; + AddSidebarEntry("Settings", "Graphics", 3); + AddWidget(path, "Toggle Fullscreen", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.Fullscreen") + .Callback([](WidgetInfo& info) { Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); }) + .Options(CheckboxOptions().Tooltip("Toggles Fullscreen On/Off.")); +#ifndef __APPLE__ + AddWidget(path, "Internal Resolution: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_INTERNAL_RESOLUTION) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier( + CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); + }) + .PreFunc([](WidgetInfo& info) { + if (mBenMenu->disabledMap.at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active && + mBenMenu->disabledMap.at(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_ON); + info.activeDisables.push_back(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON); + } else if (mBenMenu->disabledMap.at(DISABLE_FOR_LOW_RES_MODE_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_LOW_RES_MODE_ON); + } + }) + .Options( + FloatSliderOptions() + .Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but effective " + "form of anti-aliasing.") + .ShowButtons(false) + .IsPercentage() + .Format("") + .Min(0.5f) + .Max(2.0f)); +#endif +#ifndef __WIIU__ + AddWidget(path, "Anti-aliasing (MSAA): %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_MSAA_VALUE) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); + }) + .Options( + IntSliderOptions() + .Tooltip("Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of " + "rendered geometry.\n" + "Higher sample count will result in smoother edges on models, but may reduce performance.") + .Min(1) + .Max(8) + .DefaultValue(1)); +#endif + + AddWidget(path, "Current FPS: %d", WIDGET_CVAR_SLIDER_INT) + .CVar("gInterpolationFPS") + .Callback([](WidgetInfo& info) { + int32_t defaultValue = std::static_pointer_cast(info.options)->defaultValue; + if (CVarGetInteger(info.cVar, defaultValue) == defaultValue) { + info.name = "Current FPS: Original (%d)"; + } else { + info.name = "Current FPS: %d"; + } + }) + .PreFunc([](WidgetInfo& info) { + if (mBenMenu->disabledMap.at(DISABLE_FOR_MATCH_REFRESH_RATE_ON).active) + info.activeDisables.push_back(DISABLE_FOR_MATCH_REFRESH_RATE_ON); + }) + .Options(IntSliderOptions().Tooltip(tooltip).Min(20).Max(maxFps).DefaultValue(20)); + AddWidget(path, "Match Refresh Rate", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + if (hz >= 20 && hz <= 360) { + CVarSetInteger("gInterpolationFPS", hz); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + }) + .PreFunc([](WidgetInfo& info) { info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; }) + .Options(ButtonOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + AddWidget(path, "Match Refresh Rate", WIDGET_CVAR_CHECKBOX) + .CVar("gMatchRefreshRate") + .PreFunc([](WidgetInfo& info) { info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_DIRECTX).active; }) + .Options(CheckboxOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + AddWidget(path, "Jitter fix : >= % d FPS", WIDGET_CVAR_SLIDER_INT) + .CVar("gExtraLatencyThreshold") + .PreFunc([](WidgetInfo& info) { info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; }) + .Options(IntSliderOptions() + .Tooltip("When Interpolation FPS setting is at least this threshold, add one frame of input " + "lag (e.g. 16.6 ms for 60 FPS) in order to avoid jitter. This setting allows the " + "CPU to work on one frame while GPU works on the previous frame.\nThis setting " + "should be used when your computer is too slow to do CPU + GPU work in time.") + .Min(0) + .Max(360) + .DefaultValue(80)); + AddWidget(path, "Renderer API (Needs reload)", WIDGET_VIDEO_BACKEND); + AddWidget(path, "Enable Vsync", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_VSYNC_ENABLED) + .PreFunc([](WidgetInfo& info) { info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_NO_VSYNC).active; }) + .Options(CheckboxOptions().Tooltip("Enables Vsync.")); + AddWidget(path, "Windowed Fullscreen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SDL_WINDOWED_FULLSCREEN) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_NO_WINDOWED_FULLSCREEN).active; + }) + .Options(CheckboxOptions().Tooltip("Enables Windowed Fullscreen Mode.")); + AddWidget(path, "Allow multi-windows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENABLE_MULTI_VIEWPORTS) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_NO_MULTI_VIEWPORT).active; }) + .Options(CheckboxOptions().Tooltip( + "Allows multiple windows to be opened at once. Requires a reload to take effect.")); + AddWidget(path, "Texture Filter (Needs reload)", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_TEXTURE_FILTER) + .Options(ComboboxOptions().Tooltip("Sets the applied Texture Filtering.").ComboMap(textureFilteringMap)); + + path.sidebarName = "Controls"; + AddSidebarEntry("Settings", "Controls", 1); + AddWidget(path, + "This interface can be a little daunting. Please bear with us as we work to improve the experience " + "and address some known issues.\n" + "\n" + "At first glance, you may notice several input devices displayed below the 'Clear All' button. " + "Some of these might be other controllers connected to your computer, while others may be " + "duplicated controllers (a known issue). We recommend clicking on the box with the " ICON_FA_EYE + " icon and the name of any disconnected or unused controllers to hide their inputs. Make sure the " + "target controller remains visible.\n" + "\n" + "If you encounter issues connecting your controller or registering inputs, try closing Steam or " + "any other external input software. Alternatively, test a different controller to determine if " + "it's a compatibility issue.\n", + WIDGET_TEXT); + AddWidget(path, "Bindings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Bindings Window", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.BenInputEditor") + .WindowName("2S2H Input Editor") + .Options(ButtonOptions().Tooltip("Enables the separate Bindings Window.").Size(Sizes::Inline)); + + path.sidebarName = "Notifications"; + AddSidebarEntry("Settings", "Notifications", 1); + AddWidget(path, "Position", WIDGET_CVAR_COMBOBOX) + .CVar("gNotifications.Position") + .Options(ComboboxOptions() + .Tooltip("Which corner of the screen notifications appear in.") + .ComboMap(notificationPosition) + .DefaultIndex(3)); + AddWidget(path, "Duration: %.0f seconds", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gNotifications.Duration") + .Options(FloatSliderOptions() + .Tooltip("How long notifications are displayed for.") + .Format("%.1f") + .Step(0.1f) + .Min(3.0f) + .Max(30.0f) + .DefaultValue(10.0f)); + AddWidget(path, "Background Opacity: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gNotifications.BgOpacity") + .Options(FloatSliderOptions() + .Tooltip("How opaque the background of notifications is.") + .DefaultValue(0.5f) + .IsPercentage()); + AddWidget(path, "Size %.1f", WIDGET_CVAR_SLIDER_FLOAT) + .CVar("gNotifications.Size") + .Options(FloatSliderOptions() + .Tooltip("How large notifications are.") + .Format("%.1f") + .Step(0.1f) + .Min(1.0f) + .Max(5.0f) + .DefaultValue(1.8f)); + AddWidget(path, "Test Notification", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + Notification::Emit({ + .itemIcon = "__OTR__icon_item_24_static_yar/gQuestIconGoldSkulltulaTex", + .prefix = "This", + .message = "is a", + .suffix = "test.", + }); + }) + .Options(ButtonOptions().Tooltip("Displays a test notification.")); +} + +void SohMenu::AddEnhancements() { + AddMenuEntry("Enhancements", "gSettings.Menu.EnhancementsSidebarSection"); + WidgetPath path = { "Enhancements", "Camera", SECTION_COLUMN_1 }; + AddSidebarEntry("Enhancements", "Camera", 3); + + // HUD Editor + //path = { "Enhancements", "HUD Editor", SECTION_COLUMN_1 }; + //AddSidebarEntry("Enhancements", "HUD Editor", 1); + //AddWidget(path, "Popout HUD Editor", WIDGET_WINDOW_BUTTON) + // .CVar("gWindows.HudEditor") + // .WindowName("HUD Editor") + // .Options(ButtonOptions() + // .Tooltip("Enables the HUD Editor window, allowing you to modify your HUD") + // .Size(Sizes::Inline)); + + //// Item Tracker Settings + //path = { "Enhancements", "Item Tracker", SECTION_COLUMN_1 }; + //AddSidebarEntry("Enhancements", "Item Tracker", 1); + //AddWidget(path, "Popout Item Tracker", WIDGET_WINDOW_BUTTON) + // .CVar("gWindows.ItemTracker") + // .WindowName("Item Tracker Settings"); +} + +void SohMenu::AddDevTools() { + AddMenuEntry("Developer Tools", "gSettings.Menu.DevToolsSidebarSection"); + AddSidebarEntry("Developer Tools", "General", 3); + WidgetPath path = { "Developer Tools", "General", SECTION_COLUMN_1 }; + AddWidget(path, "Popout Menu", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.Menu.Popout") + .Options(CheckboxOptions().Tooltip("Changes the menu display from overlay to windowed.")); + + // dev tools windows + path = { "Developer Tools", "Collision Viewer", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Collision Viewer", 1); + AddWidget(path, "Popout Collision Viewer", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.CollisionViewer") + .Options(ButtonOptions().Tooltip("Makes collision visible on screen").Size(Sizes::Inline)) + .WindowName("Collision Viewer"); + + path = { "Developer Tools", "Stats", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Stats", 1); + AddWidget(path, "Popout Stats", WIDGET_WINDOW_BUTTON) + .CVar("gOpenWindows.Stats") + .Options(ButtonOptions().Tooltip( + "Shows the stats window, with your FPS and frametimes, and the OS you're playing on")) + .WindowName("Stats"); + + path = { "Developer Tools", "Console", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Console", 1); + AddWidget(path, "Popout Console", WIDGET_WINDOW_BUTTON) + .CVar("gOpenWindows.Console") + .Options(ButtonOptions().Tooltip( + "Enables the console window, allowing you to input commands. Type help for some examples")) + .WindowName("Console"); + + path = { "Developer Tools", "Gfx Debugger", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Gfx Debugger", 1); + AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON) + .CVar("gOpenWindows.GfxDebugger") + .Options(ButtonOptions().Tooltip( + "Enables the Gfx Debugger window, allowing you to input commands, type help for some examples")) + .WindowName("GfxDebuggerWindow"); + + path = { "Developer Tools", "Save Editor", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Save Editor", 1); + AddWidget(path, "Popout Save Editor", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.SaveEditor") + .Options(ButtonOptions().Tooltip("Enables the Save Editor window, allowing you to edit your save file")) + .WindowName("Save Editor"); + + path = { "Developer Tools", "Actor Viewer", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Actor Viewer", 1); + AddWidget(path, "Popout Actor Viewer", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.ActorViewer") + .Options(ButtonOptions().Tooltip("Enables the Actor Viewer window, allowing you to view actors in the world.")) + .WindowName("Actor Viewer"); + + path = { "Developer Tools", "Event Log", SECTION_COLUMN_1 }; + AddSidebarEntry("Developer Tools", "Event Log", 1); + AddWidget(path, "Popout Event Log", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.EventLog") + .Options(ButtonOptions().Tooltip("Enables the event log window")) + .WindowName("Event Log"); +} + +SohMenu::SohMenu(const std::string& consoleVariable, const std::string& name) + : Menu(consoleVariable, name, 0, UIWidgets::Colors::LightBlue) { +} + +void SohMenu::InitElement() { + Ship::Menu::InitElement(); + AddSettings(); + AddEnhancements(); + AddDevTools(); + + if (CVarGetInteger("gSettings.Menu.SidebarSearch", 0)) { + InsertSidebarSearch(); + } + + for (auto& initFunc : MenuInit::GetInitFuncs()) { + initFunc(); + } + + disabledMap = { + { DISABLE_FOR_GYRO_OFF, + { [](disabledInfo& info) -> bool { + return !CVarGetInteger("gEnhancements.Camera.FirstPerson.GyroEnabled", 0); + }, + "Gyro Aiming is Disabled" } }, + { DISABLE_FOR_GYRO_ON, + { [](disabledInfo& info) -> bool { + return CVarGetInteger("gEnhancements.Camera.FirstPerson.GyroEnabled", 0); + }, + "Gyro Aiming is Enabled" } }, + { DISABLE_FOR_RIGHT_STICK_OFF, + { [](disabledInfo& info) -> bool { + return !CVarGetInteger("gEnhancements.Camera.FirstPerson.RightStickEnabled", 0); + }, + "Right Stick Aiming is Disabled" } }, + { DISABLE_FOR_AUTO_SAVE_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Saving.Autosave", 0); }, + "AutoSave is Disabled" } }, + { DISABLE_FOR_NULL_PLAY_STATE, + { [](disabledInfo& info) -> bool { return gPlayState == NULL; }, "Save Not Loaded" } }, + { DISABLE_FOR_DEBUG_MODE_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gDeveloperTools.DebugEnabled", 0); }, + "Debug Mode is Disabled" } }, + { DISABLE_FOR_NO_VSYNC, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync(); + }, + "Disabling VSync not supported" } }, + { DISABLE_FOR_NO_WINDOWED_FULLSCREEN, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen(); + }, + "Windowed Fullscreen not supported" } }, + { DISABLE_FOR_NO_MULTI_VIEWPORT, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports(); + }, + "Multi-viewports not supported" } }, + { DISABLE_FOR_NOT_DIRECTX, + { [](disabledInfo& info) -> bool { + return Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != + Ship::WindowBackend::FAST3D_DXGI_DX11; + }, + "Available Only on DirectX" } }, + { DISABLE_FOR_DIRECTX, + { [](disabledInfo& info) -> bool { + return Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == + Ship::WindowBackend::FAST3D_DXGI_DX11; + }, + "Not Available on DirectX" } }, + { DISABLE_FOR_MATCH_REFRESH_RATE_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger("gMatchRefreshRate", 0); }, + "Match Refresh Rate is Enabled" } }, + /*{ DISABLE_FOR_FRAME_ADVANCE_OFF, + { [](disabledInfo& info) -> bool { return !(gPlayState != nullptr && gPlayState->frameAdvCtx.enabled); }, + "Frame Advance is Disabled" } },*/ + { DISABLE_FOR_INTRO_SKIP_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Cutscenes.SkipIntroSequence", 0); }, + "Intro Skip Not Selected" } }, + { DISABLE_FOR_ADVANCED_RESOLUTION_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); }, + "Advanced Resolution Enabled" } }, + { DISABLE_FOR_VERTICAL_RES_TOGGLE_ON, + { [](disabledInfo& info) -> bool { + return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); + }, + "Vertical Resolution Toggle Enabled" } }, + { DISABLE_FOR_LOW_RES_MODE_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_LOW_RES_MODE, 0); }, "N64 Mode Enabled" } }, + }; +} + +void SohMenu::UpdateElement() { + Ship::Menu::UpdateElement(); +} + +void SohMenu::Draw() { + Ship::Menu::Draw(); +} + +void SohMenu::DrawElement() { + Ship::Menu::DrawElement(); +} +} // namespace BenGui diff --git a/soh/soh/SohGui/SohMenu.h b/soh/soh/SohGui/SohMenu.h new file mode 100644 index 00000000000..4db6dd80890 --- /dev/null +++ b/soh/soh/SohGui/SohMenu.h @@ -0,0 +1,72 @@ +#ifndef BENMENU_H +#define BENMNEU_H + +#include +#include "UIWidgets2.hpp" +#include "Menu.h" +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "soh/Notification/Notification.h" + +namespace SohGui { + +static const std::unordered_map menuThemeOptions = { + { UIWidgets::Colors::Red, "Red" }, + { UIWidgets::Colors::DarkRed, "Dark Red" }, + { UIWidgets::Colors::Orange, "Orange" }, + { UIWidgets::Colors::Green, "Green" }, + { UIWidgets::Colors::DarkGreen, "Dark Green" }, + { UIWidgets::Colors::LightBlue, "Light Blue" }, + { UIWidgets::Colors::Blue, "Blue" }, + { UIWidgets::Colors::DarkBlue, "Dark Blue" }, + { UIWidgets::Colors::Indigo, "Indigo" }, + { UIWidgets::Colors::Violet, "Violet" }, + { UIWidgets::Colors::Purple, "Purple" }, + { UIWidgets::Colors::Brown, "Brown" }, + { UIWidgets::Colors::Gray, "Gray" }, + { UIWidgets::Colors::DarkGray, "Dark Gray" }, +}; + +static const std::unordered_map textureFilteringMap = { + { FILTER_THREE_POINT, "Three-Point" }, + { FILTER_LINEAR, "Linear" }, + { FILTER_NONE, "None" }, +}; + +//static const std::unordered_map debugSaveOptions = { +// { DEBUG_SAVE_INFO_COMPLETE, "100\% save" }, +// { DEBUG_SAVE_INFO_VANILLA_DEBUG, "Vanilla debug save" }, +// { DEBUG_SAVE_INFO_NONE, "Empty save" }, +//}; + +static const std::unordered_map logLevels = { + { DEBUG_LOG_TRACE, "Trace" }, { DEBUG_LOG_DEBUG, "Debug" }, { DEBUG_LOG_INFO, "Info" }, + { DEBUG_LOG_WARN, "Warn" }, { DEBUG_LOG_ERROR, "Error" }, { DEBUG_LOG_CRITICAL, "Critical" }, + { DEBUG_LOG_OFF, "Off" }, +}; + +static const std::unordered_map notificationPosition = { + { 0, "Top Left" }, { 1, "Top Right" }, { 2, "Bottom Left" }, { 3, "Bottom Right" }, { 4, "Hidden" }, +}; + +static const std::unordered_map damageMultiplierOptions = { + { 0, "1x" }, { 1, "2x" }, { 2, "4x" }, { 3, "8x" }, { 4, "16x" }, { 10, "1 Hit KO" }, +}; + +class SohMenu : public Ship::Menu { + public: + SohMenu(const std::string& consoleVariable, const std::string& name); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + + void AddSidebarEntry(std::string sectionName, std::string sidbarName, uint32_t columnCount); + WidgetInfo& AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType); + void AddSettings(); + void AddEnhancements(); + void AddDevTools(); +}; +} // namespace BenGui + +#endif // BENMENU_H diff --git a/soh/soh/SohGui/UIWidgets2.cpp b/soh/soh/SohGui/UIWidgets2.cpp new file mode 100644 index 00000000000..cf508eb3e5a --- /dev/null +++ b/soh/soh/SohGui/UIWidgets2.cpp @@ -0,0 +1,640 @@ +#include "UIWidgets.hpp" +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include +#include +#include +#include +#include +#include + +namespace UIWidgets { + +// Automatically adds newlines to break up text longer than a specified number of characters +// Manually included newlines will still be respected and reset the line length +// If line is midword when it hits the limit, text should break at the last encountered space +std::string WrappedText(const char* text, unsigned int charactersPerLine) { + std::string newText(text); + const size_t tipLength = newText.length(); + int lastSpace = -1; + int currentLineLength = 0; + for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) { + if (newText[currentCharacter] == '\n') { + currentLineLength = 0; + lastSpace = -1; + continue; + } else if (newText[currentCharacter] == ' ') { + lastSpace = currentCharacter; + } + + if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) { + newText[lastSpace] = '\n'; + currentLineLength = currentCharacter - lastSpace - 1; + lastSpace = -1; + } + currentLineLength++; + } + + return newText; +} + +std::string WrappedText(const std::string& text, unsigned int charactersPerLine) { + return WrappedText(text.c_str(), charactersPerLine); +} + +void Tooltip(const char* text) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", WrappedText(text).c_str()); + } +} + +void PushStyleMenu(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, UIWidgets::ColorValues.at(Colors::DarkGray)); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(Colors::DarkGray)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 15.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 3.0f); +} + +void PushStyleMenu(Colors color) { + PushStyleMenu(UIWidgets::ColorValues.at(color)); +} + +void PopStyleMenu() { + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); +} + +bool BeginMenu(const char* label, Colors color) { + bool dirty = false; + PushStyleMenu(color); + ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f, 0.0f), ImVec2(FLT_MAX, FLT_MAX)); + if (ImGui::BeginMenu(label)) { + dirty = true; + } + PopStyleMenu(); + return dirty; +} + +void PushStyleMenuItem(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, color); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0f, 15.0f)); +} + +void PushStyleMenuItem(Colors color) { + PushStyleMenuItem(ColorValues.at(color)); +} + +void PopStyleMenuItem() { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(1); +} + +bool MenuItem(const char* label, const char* shortcut, Colors color) { + bool dirty = false; + PushStyleMenuItem(color); + if (ImGui::MenuItem(label, shortcut)) { + dirty = true; + } + PopStyleMenuItem(); + return dirty; +} + +void PushStyleButton(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleButton(Colors color) { + PushStyleButton(UIWidgets::ColorValues.at(color)); +} + +void PopStyleButton() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(4); +} + +bool Button(const char* label, const ButtonOptions& options) { + ImGui::BeginDisabled(options.disabled); + PushStyleButton(options.color); + bool dirty = ImGui::Button(label, options.size); + PopStyleButton(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + return dirty; +} + +bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, + const ButtonOptions& options) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + std::string buttonText = label; + bool dirty = false; + if (CVarGetInteger(cvarName, 0)) { + buttonText = ICON_FA_WINDOW_CLOSE " " + buttonText; + } else { + buttonText = ICON_FA_EXTERNAL_LINK_SQUARE " " + buttonText; + } + if (Button(buttonText.c_str(), options)) { + windowPtr->ToggleVisibility(); + dirty = true; + } + ImGui::PopStyleVar(); + return dirty; +} + +void PushStyleCheckbox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleCheckbox(Colors color) { + PushStyleCheckbox(UIWidgets::ColorValues.at(color)); +} + +void PopStyleCheckbox() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(5); +} + +void Spacer(float height) { + ImGui::Dummy(ImVec2(0.0f, height)); +} + +void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) { + text_display_end = ImGui::FindRenderedTextEnd(text, text_end); + } else { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + if (text != text_display_end) { + window->DrawList->AddText(g.Font, g.FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + ImGui::LogRenderedText(&pos, text, text_display_end); + } +} + +bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGui::BeginDisabled(options.disabled); + + bool above = options.labelPosition == LabelPosition::Above; + bool lpFar = options.labelPosition == LabelPosition::Far; + bool right = options.alignment == ComponentAlignment::Right; + bool none = options.labelPosition == LabelPosition::None; + + std::string labelStr = (none ? "##" : ""); + labelStr.append(_label); + + const char* label = labelStr.c_str(); + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + const float square_sz = ImGui::GetFrameHeight(); + ImVec2 pos = window->DC.CursorPos; + + if (right) { + float labelOffsetX = (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz); + if (!lpFar) { + pos.x += ImGui::GetContentRegionAvail().x - (label_size.x + labelOffsetX); + } + } + float bbAboveX = lpFar ? ImGui::GetContentRegionAvail().x + : (label_size.x + (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz)); + float bbAboveY = label_size.y + (above ? square_sz : 0) + (style.FramePadding.y * 2.0f); + const ImRect total_bb(pos, pos + ImVec2(bbAboveX, bbAboveY)); + + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) { + ImGui::EndDisabled(); + return false; + } + bool hovered, held, pressed; + pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) { + *value = !(*value); + ImGui::MarkItemEdited(id); + } + PushStyleCheckbox(options.color); + ImVec2 checkPos = pos; + ImVec2 labelPos = pos; + if (options.labelPosition == LabelPosition::Above) { + checkPos.y += label_size.y + (style.ItemInnerSpacing.y * 2.0f); + } else { + labelPos.y += (square_sz / 2) - (label_size.y / 2); + } + if (options.alignment == ComponentAlignment::Right) { + checkPos.x = total_bb.Max.x - square_sz; + } else { + float labelFarOffset = ImGui::GetContentRegionAvail().x - label_size.x; + float labelOffsetX = above ? 0 : (lpFar ? labelFarOffset : (style.ItemInnerSpacing.x * 2.0f) + square_sz); + labelPos.x += labelOffsetX; + } + const ImRect check_bb(checkPos, checkPos + ImVec2(square_sz, square_sz)); + ImGui::RenderNavHighlight(total_bb, id); + ImGui::RenderFrame(check_bb.Min, check_bb.Max, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive + : hovered ? ImGuiCol_FrameBgHovered + : ImGuiCol_FrameBg), + true, style.FrameRounding); + ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark); + bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + if (mixed_value) { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all + // widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } else if (*value) { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } + RenderText(labelPos, label, ImGui::FindRenderedTextEnd(label), true); + PopStyleCheckbox(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + return pressed; +} + +bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options) { + bool dirty = false; + bool value = (bool)CVarGetInteger(cvarName, options.defaultValue); + if (Checkbox(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +void PushStyleCombobox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); +} + +void PushStyleCombobox(Colors color) { + PushStyleCombobox(UIWidgets::ColorValues.at(color)); +} + +void PopStyleCombobox() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(9); +} + +void PushStyleSlider(Colors color_) { + const ImVec4& color = UIWidgets::ColorValues.at(color_); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(1.0, 1.0, 1.0, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(1.0, 1.0, 1.0, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); +} + +void PopStyleSlider() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(6); +} + +bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label, *value); + } + } + if (options.showButtons) { + if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) { + *value -= options.step; + if (*value < options.min) + *value = options.min; + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize("+").x + 20.0f + 3.0f)); + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, value, &options.min, &options.max, options.format, + options.flags)) { + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) { + *value += options.step; + if (*value > options.max) + *value = options.max; + dirty = true; + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultValue); + if (SliderInt(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +void ClampFloat(float* value, float min, float max, float step) { + int ticks = 0; + float increment = 1.0f; + if (step < 1.0f) { + ticks++; + increment = 0.1f; + } + if (step < 0.1f) { + ticks++; + increment = 0.01f; + } + if (step < 0.01f) { + ticks++; + increment = 0.001f; + } + if (step < 0.001f) { + ticks++; + increment = 0.0001f; + } + if (step < 0.0001f) { + ticks++; + increment = 0.00001f; + } + if (step < 0.00001f) { + ticks++; + increment = 0.000001f; + } + int factor = 1 * std::pow(10, ticks); + if (*value < min) { + *value = min; + } else if (*value > max) { + *value = max; + } else { + int trunc = (int)std::round(*value * factor); + *value = (float)trunc / factor; + } +} + +bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + float valueToDisplay = options.isPercentage ? *value * 100.0f : *value; + float maxToDisplay = options.isPercentage ? options.max * 100.0f : options.max; + float minToDisplay = options.isPercentage ? options.min * 100.0f : options.min; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, valueToDisplay); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label, valueToDisplay); + } + } + if (options.showButtons) { + if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) { + *value -= options.step; + ClampFloat(value, options.min, options.max, options.step); + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize("+").x + 20.0f + 3.0f)); + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_Float, &valueToDisplay, &minToDisplay, &maxToDisplay, + options.format, options.flags)) { + *value = options.isPercentage ? valueToDisplay / 100.0f : valueToDisplay; + ClampFloat(value, options.min, options.max, options.step); + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) { + *value += options.step; + ClampFloat(value, options.min, options.max, options.step); + dirty = true; + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options) { + bool dirty = false; + float value = CVarGetFloat(cvarName, options.defaultValue); + if (SliderFloat(label, &value, options)) { + CVarSetFloat(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor) { + Color_RGBA8 color = CVarGetColor(cvarName, defaultColor); + ImVec4 colorVec = ImVec4(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f); + bool changed = false; + PushStyleCombobox(Colors::Gray); + if (ImGui::ColorEdit3(label, (float*)&colorVec, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoBorder)) { + color.r = (uint8_t)(colorVec.x * 255.0f); + color.g = (uint8_t)(colorVec.y * 255.0f); + color.b = (uint8_t)(colorVec.z * 255.0f); + color.a = (uint8_t)(colorVec.w * 255.0f); + CVarSetColor(cvarName, color); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + changed = true; + } + PopStyleCombobox(); + return changed; +} + +void DrawFlagArray32(const std::string& name, uint32_t& flags) { + ImGui::PushID(name.c_str()); + for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint32_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + std::string label = fmt::format("0x{:02X} ({})", flagIndex, flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, + CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray16(const std::string& name, uint16_t& flags) { + ImGui::PushID(name.c_str()); + for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint16_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + std::string label = fmt::format("0x{:02X} ({})", flagIndex, flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, + CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray8(const std::string& name, uint8_t& flags) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + std::string label = fmt::format("0x{:02X} ({})", flagIndex, flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, + CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray8Mask(const std::string& name, uint8_t& flags) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + std::string label = fmt::format("0x{:02X} ({})", bitMask, flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, + CheckboxOptions{ { .tooltip = label.c_str() } }.LabelPosition(LabelPosition::None))) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); +} +} // namespace UIWidgets diff --git a/soh/soh/SohGui/UIWidgets2.hpp b/soh/soh/SohGui/UIWidgets2.hpp new file mode 100644 index 00000000000..75e66283e5a --- /dev/null +++ b/soh/soh/SohGui/UIWidgets2.hpp @@ -0,0 +1,717 @@ +#ifndef UIWidgets2_hpp +#define UIWidgets2_hpp + +#include +#include +#include +#include +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include +#include +#include "soh/ShipUtils.h" +#include "soh/ShipInit.hpp" + +namespace UIWidgets { + + using SectionFunc = void(*)(); + + //struct TextFilters { + // static int FilterNumbers(ImGuiInputTextCallbackData* data) { + // if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) { + // return 0; + // } + // return 1; + // } + + // static int FilterAlphaNum(ImGuiInputTextCallbackData* data) { + // const char* alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ0123456789"; + // if (data->EventChar < 256 && strchr(alphanum, (char)data->EventChar)) { + // return 0; + // } + // return 1; + // } + //}; + + /*std::string WrappedText(const char* text, unsigned int charactersPerLine = 60); + std::string WrappedText(const std::string& text, unsigned int charactersPerLine = 60); + void Tooltip(const char* text);*/ + + // mostly in order for colors usable by the menu without custom text color + enum Colors { + Red, + DarkRed, + Orange, + Green, + DarkGreen, + LightBlue, + Blue, + DarkBlue, + Indigo, + Violet, + Purple, + Brown, + Gray, + DarkGray, + // not suitable for menu theme use + Pink, + Yellow, + Cyan, + Black, + LightGray, + White, + NoColor + }; + + const std::unordered_map ColorValues = { + { Colors::Pink, ImVec4(0.87f, 0.3f, 0.87f, 1.0f) }, + { Colors::Red, ImVec4(0.55f, 0.0f, 0.0f, 1.0f) }, + { Colors::DarkRed, ImVec4(0.3f, 0.0f, 0.0f, 1.0f) }, + { Colors::Orange, ImVec4(0.85f, 0.55f, 0.0f, 1.0f) }, + { Colors::Yellow, ImVec4(0.95f, 0.95f, 0.0f, 1.0f) }, + { Colors::Green, ImVec4(0.0f, 0.55f, 0.0f, 1.0f) }, + { Colors::DarkGreen, ImVec4(0.0f, 0.3f, 0.0f, 1.0f) }, + { Colors::Cyan, ImVec4(0.0f, 0.9f, 0.9f, 1.0f) }, + { Colors::LightBlue, ImVec4(0.0f, 0.24f, 0.8f, 1.0f) }, + { Colors::Blue, ImVec4(0.08f, 0.03f, 0.65f, 1.0f) }, + { Colors::DarkBlue, ImVec4(0.03f, 0.0f, 0.5f, 1.0f) }, + { Colors::Indigo, ImVec4(0.35f, 0.0f, 0.87f, 1.0f) }, + { Colors::Violet, ImVec4(0.5f, 0.0f, 0.9f, 1.0f) }, + { Colors::Purple, ImVec4(0.31f, 0.0f, 0.67f, 1.0f) }, + { Colors::Brown, ImVec4(0.37f, 0.18f, 0.0f, 1.0f) }, + { Colors::LightGray, ImVec4(0.75f, 0.75f, 0.75f, 1.0f) }, + { Colors::Gray, ImVec4(0.45f, 0.45f, 0.45f, 1.0f) }, + { Colors::DarkGray, ImVec4(0.15f, 0.15f, 0.15f, 1.0f) }, + { Colors::Black, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)}, + { Colors::White, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, + { Colors::NoColor, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)}, + }; + + namespace Sizes { + const ImVec2 Inline = ImVec2(0.0f, 0.0f); + const ImVec2 Fill = ImVec2(-1.0f, 0.0f); + } + + enum LabelPosition { + Near, + Far, + Above, + None, + Within, + }; + + enum ComponentAlignment { + Left, + Right, + }; + + struct WidgetOptions{ + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + Colors color = Colors::NoColor; + + WidgetOptions& Color(Colors color_) { + color = color = color_; + return *this; + } + WidgetOptions& Tooltip(const char* tooltip_) { + tooltip = tooltip_; + return *this; + } + WidgetOptions& Disabled(bool disabled_) { + disabled = disabled_; + return *this; + } + }; + + struct ButtonOptions : WidgetOptions { + ImVec2 size = Sizes::Fill; + Colors color = Colors::Gray; + + ButtonOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + ButtonOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + ButtonOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct CheckboxOptions : WidgetOptions { + bool defaultValue = false; // Only applicable to CVarCheckbox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Near; + Colors color = WidgetOptions::color = Colors::LightBlue; + + CheckboxOptions& DefaultValue(bool defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + CheckboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + CheckboxOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + CheckboxOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + CheckboxOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct ComboboxOptions : WidgetOptions { + std::unordered_map comboMap = {}; + uint32_t defaultIndex = 0; // Only applicable to CVarCombobox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + ImGuiComboFlags flags = 0; + Colors color = Colors::LightBlue; + + ComboboxOptions& ComboMap(std::unordered_map comboMap_) { + comboMap = comboMap_; + return *this; + } + ComboboxOptions& DefaultIndex(uint32_t defaultIndex_) { + defaultIndex = defaultIndex_; + return *this; + } + ComboboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + ComboboxOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + ComboboxOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + ComboboxOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct IntSliderOptions : WidgetOptions { + bool showButtons = true; + const char* format = "%d"; + int32_t step = 1; + int32_t min = 1; + int32_t max = 10; + int32_t defaultValue = 1; + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImGuiSliderFlags flags = 0; + + IntSliderOptions& ShowButtons(bool showButtons_) { + showButtons = showButtons_; + return *this; + } + IntSliderOptions& Format(const char* format_) { + format = format_; + return *this; + } + IntSliderOptions& Step(int32_t step_) { + step = step_; + return *this; + } + IntSliderOptions& Min(int32_t min_) { + min = min_; + return *this; + } + IntSliderOptions& Max(int32_t max_) { + max = max_; + return *this; + } + IntSliderOptions& DefaultValue(int32_t defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + IntSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + IntSliderOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + IntSliderOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + IntSliderOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct FloatSliderOptions : WidgetOptions { + bool showButtons = true; + const char* format = "%f"; + float step = 0.01f; + float min = 0.01f; + float max = 10.0f; + float defaultValue = 1.0f; + bool isPercentage = false; // Multiplies visual value by 100 + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImGuiSliderFlags flags = 0; + + FloatSliderOptions& ShowButtons(bool showButtons_) { + showButtons = showButtons_; + return *this; + } + FloatSliderOptions& Format(const char* format_) { + format = format_; + return *this; + } + FloatSliderOptions& Step(float step_) { + step = step_; + return *this; + } + FloatSliderOptions& Min(float min_) { + min = min_; + return *this; + } + FloatSliderOptions& Max(float max_) { + max = max_; + return *this; + } + FloatSliderOptions& DefaultValue(float defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + FloatSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + FloatSliderOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + FloatSliderOptions& IsPercentage(bool isPercentage_ = true) { + isPercentage = isPercentage_; + format = "%.0f"; + min = 0.0f; + max = 1.0f; + return *this; + } + FloatSliderOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + FloatSliderOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + void PushStyleMenu(const ImVec4& color); + void PushStyleMenu(Colors color = Colors::LightBlue); + void PopStyleMenu(); + bool BeginMenu(const char* label, Colors color = Colors::LightBlue); + + void PushStyleMenuItem(const ImVec4& color); + void PushStyleMenuItem(Colors color = Colors::LightBlue); + void PopStyleMenuItem(); + bool MenuItem(const char* label, const char* shortcut = NULL, Colors color = Colors::LightBlue); + + void PushStyleButton(const ImVec4& color); + void PushStyleButton(Colors color = Colors::Gray); + void PopStyleButton(); + bool Button(const char* label, const ButtonOptions& options = {}); + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const ButtonOptions& options = {}); + + void PushStyleCheckbox(const ImVec4& color); + void PushStyleCheckbox(Colors color = Colors::LightBlue); + void PopStyleCheckbox(); + void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash); + bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {}); + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {}); + + void PushStyleCombobox(const ImVec4& color); + void PushStyleCombobox(Colors color = Colors::LightBlue); + void PopStyleCombobox(); + + void Spacer(float height); + + /*using ComboVariant = std::variant&, const std::vector&>; + + bool Combobox(const char* label, int32_t* value, ComboVariant comboSource, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (const auto& pair : comboMap) { + if (strlen(pair.second) > 1) { + if (ImGui::Selectable(pair.second, pair.first == *value)) { + *value = pair.first; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text("%s", label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCombobox(const char* label, const char* cvarName, ComboVariant comboSource, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboSource, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + }*/ + + template + bool Combobox(const char* label, T* value, const std::unordered_map& comboMap, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (const auto& pair : comboMap) { + if (strlen(pair.second) > 1) { + if (ImGui::Selectable(pair.second, pair.first == *value)) { + *value = pair.first; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text("%s", label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + size_t currentValueIndex = static_cast(*value); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboVector.at(currentValueIndex)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboVector.at(currentValueIndex)).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < comboVector.size(); ++i) { + auto newValue = static_cast(i); + if (strlen(comboVector.at(i)) > 1) { + if (ImGui::Selectable(comboVector.at(i), newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text(label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const char* (&comboArray)[N], const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + size_t currentValueIndex = static_cast(*value); + if (currentValueIndex >= N) { + currentValueIndex = 0; + } + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboArray[currentValueIndex]).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboArray[currentValueIndex]).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboArray[currentValueIndex], options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < N; ++i) { + auto newValue = static_cast(i); + if (strlen(comboArray[i]) > 1) { + if (ImGui::Selectable(comboArray[i], newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text("%s", label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const std::unordered_map& comboMap, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboMap, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboVector, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const char* (&comboArray)[N], const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboArray, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + void PushStyleSlider(Colors color = Colors::LightBlue); + void PopStyleSlider(); + bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options = {}); + bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options = {}); + bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options = {}); + bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options = {}); + bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor); + void DrawFlagArray32(const std::string& name, uint32_t& flags); + void DrawFlagArray16(const std::string& name, uint16_t& flags); + void DrawFlagArray8(const std::string& name, uint8_t& flags); + void DrawFlagArray8Mask(const std::string& name, uint8_t& flags); +} + +#endif /* UIWidgets_hpp */