From 12d3c59172846cc2890a714bc1d0ed9dd118b9e2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 27 Jun 2024 11:04:55 +0800 Subject: [PATCH] windows portable loading ui (#8490) Signed-off-by: 21pages --- Cargo.lock | 62 ++++++++- libs/portable/Cargo.toml | 3 + libs/portable/src/main.rs | 7 + libs/portable/src/res/label.png | Bin 0 -> 1234 bytes libs/portable/src/res/spin.gif | Bin 0 -> 59332 bytes libs/portable/src/ui.rs | 232 ++++++++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 libs/portable/src/res/label.png create mode 100644 libs/portable/src/res/spin.gif create mode 100644 libs/portable/src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 54bef4444..a3db6573b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2139,7 +2139,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad46a0e6c9bc688823a742aa969b5c08fdc56c2a436ee00d5c6fbcb5982c55c4" dependencies = [ - "libm", + "libm 0.2.8", ] [[package]] @@ -3493,6 +3493,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libm" version = "0.2.8" @@ -3813,6 +3819,22 @@ dependencies = [ "tempfile", ] +[[package]] +name = "native-windows-gui" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7003a669f68deb6b7c57d74fff4f8e533c44a3f0b297492440ef4ff5a28454" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "newline-converter", + "plotters", + "plotters-backend", + "stretch", + "winapi 0.3.9", + "winapi-build", +] + [[package]] name = "ndk" version = "0.7.0" @@ -3891,6 +3913,15 @@ dependencies = [ "log", ] +[[package]] +name = "newline-converter" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "nix" version = "0.23.2" @@ -4574,6 +4605,24 @@ dependencies = [ "time 0.3.30", ] +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "num-traits 0.2.17", + "plotters-backend", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + [[package]] name = "png" version = "0.17.10" @@ -5390,6 +5439,7 @@ dependencies = [ "brotli", "dirs 5.0.1", "md5", + "native-windows-gui", "winapi 0.3.9", "winres", ] @@ -5948,6 +5998,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" +[[package]] +name = "stretch" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0dc6d20ce137f302edf90f9cd3d278866fd7fb139efca6f246161222ad6d87" +dependencies = [ + "lazy_static", + "libm 0.1.4", +] + [[package]] name = "strsim" version = "0.8.0" diff --git a/libs/portable/Cargo.toml b/libs/portable/Cargo.toml index e39762a30..d0305b6b0 100644 --- a/libs/portable/Cargo.toml +++ b/libs/portable/Cargo.toml @@ -14,6 +14,9 @@ dirs = "5.0" md5 = "0.7" winapi = { version = "0.3", features = ["winbase"] } +[target.'cfg(target_os = "windows")'.dependencies] +native-windows-gui = "1.0" + [package.metadata.winres] LegalCopyright = "Copyright © 2024 Purslane Ltd. All rights reserved." ProductName = "RustDesk" diff --git a/libs/portable/src/main.rs b/libs/portable/src/main.rs index 1ffc8aa5d..48372d68b 100644 --- a/libs/portable/src/main.rs +++ b/libs/portable/src/main.rs @@ -8,6 +8,8 @@ use std::{ use bin_reader::BinaryReader; pub mod bin_reader; +#[cfg(windows)] +mod ui; #[cfg(windows)] const APP_METADATA: &[u8] = include_bytes!("../app_metadata.toml"); @@ -119,6 +121,11 @@ fn main() { let click_setup = args.is_empty() && arg_exe.to_lowercase().ends_with("install.exe"); let quick_support = args.is_empty() && arg_exe.to_lowercase().ends_with("qs.exe"); + #[cfg(windows)] + if args.is_empty() { + ui::setup(); + } + let reader = BinaryReader::default(); if let Some(exe) = setup( reader, diff --git a/libs/portable/src/res/label.png b/libs/portable/src/res/label.png new file mode 100644 index 0000000000000000000000000000000000000000..6876c7935afc33e4f62d0069d7fab30dabd121d9 GIT binary patch literal 1234 zcmeAS@N?(olHy`uVBq!ia0vp^2|%pC!3-pC$@QiIDaPU;cPGZ1Cw1z99L@rd$YKTt zZeb8+WSBKa0;u3nfKP}kQ1bsM7|J1V^=YUzFcznk1o;Is@H=P;Yuf)`nf9^QXIbVf zmb~SgTE&!CAJ{&_n<;Vbp7Sf5qx5UH7;xk)*gI2|@yMO`ikJQO|2k3e;fJii{m+L= zB3P3bC2`sEt2*ZlpxB=<}J_ajpYU2QX!@JWGE>}h&VZVU`e?4B-;Ar*6y zQx+H|$ZSox(iF(e*38<|(^IdxrQxak=lnZbHzXwM3yh5FmL6c5cRy$T$LIs!CqGv9 z)m6A$z{AZV;rPI`Sz<=rsRIhki{^XZ4gdA+?c`74a&E`;_n1pcm^3uyyGrO!&S{i; z%$v6LbyUyuDJ=7iJmN0fET1*WI!I`O*aq~|6l7^K-`9j-Vv=i}qj)66Q- z4=Q?YC(QpWc-vN??^1B%^mXk^Ldti_H$)ydk@)e&y31wj_v-aT*OX68Y`pK+-^=6Y zZfkB}z+z;S7r67(|HI|H7JLd~Kh~yk+VDOCBs5MpC9{(7`< z!Gd_LA4-*;Z)Dh7_#4D52?@ zFOps=8N@G}v5{f5-8=i(pF0k-Zf8BxU@`e@_2K*%oZl~=YC3uTm&B)g9AAZ&&zkcq q!7ENZ+-zePyJM?y*1-we85k;iZYXA&dFuibErX}4pUXO@geCx`f))+{ literal 0 HcmV?d00001 diff --git a/libs/portable/src/res/spin.gif b/libs/portable/src/res/spin.gif new file mode 100644 index 0000000000000000000000000000000000000000..44b2e2e62cf04fd7f19ea414c885a9e7369bc432 GIT binary patch literal 59332 zcmeFaWmMMd*7ki7(se1FiiC6u3Mvf(Qc6h+NOw0V(nv{ncXxO9MR#|1gYO?*YwdOK zz4ra=_3rl_V?9gx#Q5{+JjdYtjbk3ioKr|x@X6D6?VxthDF`$@Jv}osGdnvwH#avw zKfk=Zys@#dv$Jz>aBzBhdUkepetv#&adCNh`Ss_|&l~vL-N1vd$l?OhZ}@m51(@j> zVW2=D(1UYWL>Lenh!*%A_$O%xfe>y$Lx!^hf*$Pg3TwQJA zwfpXT8<8^1-smj$;a4W2Y(+C{B<_ zW~rnL=gHZU{}Yi2j1WeJah%VOkusKfloHocRM+2SVCW)Y;l*g`=Hf2#7?3Wdy7}=` zP`pQCGD*`h^)9E>G_3TBH2X;LY5bEw?E2eqZ{}2;q>7HG=>~92UU6SSDDc;LIXl~< zxMN=@XV@nCQDY=ty@gH6yn%ndM(rBisDP1$A<`V5gWSVTlUdCcoMq1hr@vLuPLZ`$ z_yRn)Rn!aZR$n|Mt4{~5%agTTIyGRiT{fqHj9s=AT4g9YpPIE(tPrueQ`MJPyi>iQ za%Z<@CzOA;_E_9)w{F?5dAEN40(q~YAL-RzNZg>iin?rl_FiOCuT6dfIo< zM)Cyqn@m-Dfi#A#-Kp6JowQ|J2VHbn_syYjiPR3E82g@?_pr}a7xr=-P8IcZL!%b= z^WFQL4dHnh!`j=x>}Vk9*toDyn$7hC3Im?NX4a z*4ex#W$xL6FF|R=ax53w(;?^2;up(kF=+D37`8X(<92B~HvEO6!B185+vqR1iracP zr|(^(Iqd0dT2}G5zQMdu?qd0BFVHtC;2_ZRMz!i#T_d}{s&>nSPW6k+a;{y+H$*78x4`)>m>opW_iXC#EaZMbBb=Ki zee}FuD&GdXdJcv%ooCiG-}UY@Vg&F(19+mxkm4vF;f+l|S;3wgqi+I=_RA*xi9Pox z-3e&c?M=iNdmd!mD2W&zFPlm6_dQ>}O1vA^-b}%|x{u&}bSvbnjrySsaMc=(g4e>;f$JkTH9002$AzZf)OkcnTF=g&8J-lI|J zE-zSY3wS6R`>LYweG>@8DB}`u{;>j@*=NKc*nD*mVaZSB`4H>IV5Xcu8i=)FbJPr` zU8`sC*l?`C+9x&U@YL{*xm#DQ?&K4r%1+xpgPp4*?KOyyMBYuck<~V1#Z6u9&h@29 z*icJat3GkH$ZaIlSRI=7b8swf2LYcHutwx(B4zI-6tO(zWaX5KODQ^o${V109Tz4(OOzZJlqHm$? z-NXQ1J(;9ndWF}?;lv^`DbYwAuT$eMp30;p&z$R~r&Dm~WhARaQfJ=C6^Q7H3qLQqSaFkJmTz%Cczg?|U*ZrYu+Ks2x%q$yyMx&X zSfbf-R0ZdA*x=pIVvtd753#@r{V3(INn>+E@TA29UvSuzHIBh(9p3(QS+#jxRp8ua zVU%?x;e%M4IqFmcGx3EN3Yyhe3*B2(9OPkb=WV&aD6l+XMBA;rk!_v zGG)DceLCmCaC5d4W_)wLmX>#Ou~oKvbGg?hVbA>wsE~dKs_)`&adB~Vb#-fNYj1Dw z=;-LD?E8Z!?dPYz;|&0y!ur1nRAMf3F4~n5eEMt?<3+l#W&&Bn3KPYfLzx2bmSUGI zTVo0KrMZT7^hU!_76-X8^{2bTY}V*yhF3O5k)2^laNA0y-D_RDvSl}BPj}ZX%Wizo zW(GKSs~0NLSXJ$fFS?hc3e!-DcQ3m)SL;^noh^j7X0C@-g%Q`R@zr3wrQ<)q53S05 zywB9Gq=sQdHV@@?$7EIrhgdd2@GeE_ygi{?Ij$Ez)RSn9N9vmKu9V(kf!;^Lp|L)P zSnLtL*pRWw_mgi~XWzrfP$pY5LZ43t(%iM}55ju9uF2ajlROnHua&VJs$`WB1X6QH zT#H5km-Bv2?Xw?ec)fmG<*40>t*a)mx7dZSCVMlC*beWG7NIJsq zY@L)c-hy_kFa2aWR;7cqIw?{@tu`oABegoy(_&Px^b)B8C)Q-s#52<}Z^AwfQ%C1J zRjx$6c)PKjTYa*znO6^|2mv*txky2^lTPXvbQ4Dz6sj;!ZVB|gs7fgw;83J1exuGw zS28JUA5k{0g_&C(9C&HS3tKE&&%2sP5K*~Fy0lZZ*V?tMafIE~U$f1eyj$ygENZ|9 zTFmONho#ioYd|I^G^s^lILR;gDl&!9)JbYpz;~CXnz8K;;R#bazVR!wcQ_i+rtN5f zN`-24T$l&V)aHcpJ#4cmf!!`KQ!L$Fc~fEid}&G+Ee;PpAM}v6Wgn`bOTa$v6-CdM z8x<11(#Wlyhcf4F@t z=-lU)Bjk9GK5r7^;fr!1)Hk8!`NH?aZ22`+>7`a8`I>E4C1_AN8ejMm+cl=j?HFxj zYV=g{hSs4~trZp_n`|e~`q=O2?;08J*1|Jf?KR^VU+uS3{)eD~!N&N7P6)rF6Hu32 zT3T9LTif2=-rwIpIXU@Rj{G~m!avXS$2S1b3H*N%omd~&pM$2m;~x_|udiU$u1I)u z?{N>lKdsT8imTElzgM)OTPqpU9`Sq_#Wv~RSWCg_i5 z7}DObo1Ai3g-$BoEFRwwy|kyK&tt~#R-WZJ=s;x10fRL}h`D)MQd^va3A~fI( zSB(>IM+(-tuXgR2d|wN}E}6vK6LAdu!TW7G*xg4U9P+7xE}a0tuK9%6r@@fhLYBO zAHszde@mwdas@ees+DjSTYyelPLEe24ZJ(h8SME*{a-7l2-(1_{C*O=M+P!gXck)r#X~=i`7yX@lE{IkO8}NXKal zJ9x9U-Vbs_1(uAy4y7wWM7_TQLN=W=)SUh3(_g+6s&7>6pOQtxx8>c{U$w_fVVsLP zJekXTwh}c~dquyLSF;0GjoVN)U~j?)_0=1<361EAp&oqC1h)y5b1RPzPyZF}t$1Q; z1)~d^bzpO0nyJwgUFCP-!3Z+(Q^YwK_c%`b7Ioa+uMX=&U-o6>=Uo-p;}_gSG3^xD z6_pyqL2o(lQ!aX`rJ%1;D5fk^F`u_}}5^yGORZ zzW&`K190@y8vEmi>*v?M;|%~fLjS)6N6TGYsJQvap!(N6@$7xuuO`eP{pmtsJFhS; z^@g*=E-kL{tqjJW>Q?32R~3s-SJ|FYINq}{tZZ@|WOX96H5*}$@EExyybxU0{?uO*_bPPm%;fxMRoZr)B?URe7$LH}2=FU`H>+avk@7W2i6o?y+N>kzvrtVVUOH$j=fLCnm$*+U~90e;s)m;ve&13~| zMB#-Ms^*TjB&;0zHdRJF6;VrAtF(h$Mz7U&`K8f_aEi3qN(Wht)rl=xwB1TaYMf&t zUV5bF-HlXPkKT%)MCFgekAeg2Dw2~p&({}Iqpdd9)8ehD>(imt`0F#$E?#bArY3rC z_}yd`+6876kKpQO=L}qAdX;r@WaXA9jL_ubK@n_eHeE#agSyf30}9%BL$eDJ9&c_H z_3vDa6(=)m(iNe+&!W?q@|)-{8>J@ODfbG|-{D>R=(AnvQ=ynyw$D~cRLyT-xF*zgGtfDjaN^{yHk7s?(VGQ4a-%ene9MztGWSkvZtrzyF1Qa;+i z4*P_w{`LdjW;2x?aafj)3-MRx6`0en0(-580kMPyrHs>ub*{u(BP8JyI3TL6A2uj) zFD9$Ng1ibmRHy!YG@sDau_Wv5J%Z9rd(dC3C5IVZ ztf!~tUTkES?Ober_rCrZM-F08P`^kZL~Qq z29dg4%{5&H2x34}H%GZ6D7W`}+*d$I@W2bjRd+!a_6+w%E-07OVr;b`SMNMtT9;qbw>AZQ&5 z(262V@%l;>2bGIUgvE$1Wemb2?vW^mlXc2hh*MN+@gFJjL*hIsymds(?{2ImX{n^F zB?ni&4T28PLdr;ZBeSudOr}ennH1xsw~_eZbLK|;>zEDdxEs9>UnQ4mA1pQuge>0|>K|xjJdz5`%LecL2w)<@q%$2AiQw6+_(hMd#X}1p7Ux>(3PW(58LOdwCCES#)v3#}unSzq~4fpsgzzt4VQb7I(;SZOe@) z7^4ObyU-D_4QtrvgpCUr_gIcHowis_8VT{AsIW>bos>=LCRLwwVs1Mg@tH1$SkKwZ zNtY|T$dQ!``1Fx83;OfjFfF2MOWQWU4Te+*Mw#d4t-_dNUeqOt=H{<^nZueWKeV^F z5XA9e=d6I4_Pd0x+*LW@Ax@`=YZxD{@Y*pxBKEEZSciXrzi)B|8SOW^M=YJ-hWsx4 zfg2LV(!9qV!}2*@GE&V%UJT~YL|&{|gmE86cO48Z){(V%UY3>ghjv7jYm2;`-niod z{Lo%Yg4=W*5F2qTR^?#g6rtr`K-L-zf<_?3NDWsf4d7OSNzPb_dYcW(6ct9kA*km{%Kv3^>QZSo>%xxs%bxJfnhfHcb zHudYYWN2!c^mJ1E*BRNJ=en6U=|&UOSz(Ql>&Zn{ks&#WsYBYizG#(LdG@uiTXEWW z6D!%4R<`|N@BHv`B6?-@w@b!ezvm8xwGagf!hIsx37QGD4=i6Ziqfc9iH*uFrz-5C zFW+s9N~qqhRLrXxpXW3dyiAyYg}AeF=;>+aA4%a zltYCy<8dR`S5i~Y?Wavsr*Y&ySqoT9zAu|KRm40~a#TGko5w^JHxcyNtYT31*T<|_ z+I{WEAQUzj^>pE@naUHP;}N$4p&0#OdY24s8p+MvCgRKC+=st0yw!CH!4?KY9*x$Ji`q>vdK-(v*UvGPE!$I7H`;j!v1K;9ce|pvNTL%j zj<+al`+A(7HJ=}CQaXEbIbUCvGlc~>Z9g4~bN-5F6X}8sE}!MCVoDEiMHP57?{Xni zKIe8%3t`@!P(3`JH{5M3z=Q0y)`HhVQt1V6l!P@>4MvWk1)hN6Gf4-k+*A$Uh3hkj zEiamva^QV256zcrbia5|!NNpp^&#@ajA0-ptMz4ZS#`jJ($o?T4bw@@pa^3O0R)(l zS;+% z!B%?fDG}bsYIg-YuNq-kf4An07l`t8GA}XIfZ%Bv^?&#J@9$P+!1Pf#ng_=$qBX$JNUoHBjdoSm#Bu&E#fJJFYU98R)N5_nyf zHW5?J7}a*aHKOH-G^UqajddmF=#6bsvRFS1^0TYU%ya7R%Pk@{-^z;9_2X== zBw)4Qt&%`>*lYSz35nY%pYY3pt+;~|W zE`gKrdE3L0G7=)^i_@!V$~Y$woK1`~`nL9c=(C>DfRD&*;Us)zbmdB}XfiqpE?e)P zOt|kqC=YPIf5Lv>Eg-}*z!OO-b-|mmGKf2L8OqiJXuc@wu8&mmT|&XpcMW{;k_#D&c?C&(pyl3BFRs-lRP;{OCeP{)QTFE z8Yv=@p5_LPpPBeJFmgpC4Q(PM^Cqr#NH0q3_&g<5hQ3($dxq~NmLzu&3??T?u#tju z%cqsiKCrMqbS%3Fn}=Zgrc6f7-SZb@edvJvlEV%ARO z;>gNQ6+%nYPW2HIK&TexI)v5fu&AR9bXyv<^i?;gPZf@Mnd&#((S=31RSXp|A zw=tRn90}Q(@7K7qKM+HM*i6zznROJ|;L?}lWIBEr?Bp{en%53%6hRT5x<^_&{ zlfyu6q5==1%29KwTjbS+@?1uj82v3hfNHb zVPj5J%4Z`qy&m$jjwV-YV#RTM*XO4sF1=Ei1dew|({MiS3m4DvZ9QI7Q^62KfP6xI z+di#=M^XO35Yui=QT2iM8iofFs@j6rBljl@hD5ap^4{nLp8Y(Xrf41u z*I$5}4@TAA5jAY3Zi1`T#D*81dae;oE5>NTD{uhH2AWZSeg8()rKRe^1R%%BgU zTAAy&d5MfyaG$6*(i5EQz!~YO-kBLF85l^3&oBhDNlAa)BNuzP|&7W20va<$=*0x0<*KHf4h z#1Q)KUF}yDzJEr~M@W<14^49F6WRWdLE5YhAtk#|o7PA4po@`&%)D$*L6)V0y{a0_<;b^aOCpcuh54p8HZIpjfMjj6A3c^6YMgqkpA(ctLy{AA$0 z{dU-dKJqpOggH&woL@oWx%K3*{WY!B3R2zN^=T}dA#o+bX(q@q3S5Wjvt$%1R9&hlD}6|4 zzl>+u!?3~T9BeNbD`ZzKnCj}cdN3irVH!- ze+9c81dz)#R6WmzN=`wU>8$w&8j?BFCvA)UUl`>n+muh`V16}0{nn=#&K9A%+B`N2$*ZyO1*ghRF4U|u@pD)k7`9Fn znK~lSFR0g(gJps0AR{;)fSs9lhCqxvwO)E0GwVi1x|M<+Pe$p+M&?a!>jiac;fUQv zTIotAbx!38l73!ct_Vhc22vD8K_otbRCXuZZ6r{TCPxyHP;DCufmN}-bW#PFlL1dA zNnN&NhrCl39;BEnu=+7-J9#5h->7P|R#B&VU*!|1W(V3kuNoOot)BPlgkZG}L`jxk zkF>35Qd9n*I=2y-CAz<9cETsJd5T%OpatQnFH>o@DG<*fVaV=p$MDcHD}zdVw%19k zxH;Z+hn4KG8_}z~@con3t%7cpVS}934|j47d!MNqunss;)g1Mr;)yYS2fmxOg zn=G2h&*tHRgX9^aIwzxawa<*F|kWL@9bCg zuQ{H0*z9-;?}+Ziv*0fw*|{Q@XLd1((|dEiStn8OzXmn_9qc6T{wn4We<$XEPT}vp zSU{UF(2E85U_aZ0f4==c&J6&3yZ4t9bEp{qHQ$u~2j6i1cfQ#R?9G?>Ydb-z`H#k& z^U_oxz#ORcmcBGDNG;#hE#K^~A#7lm@#V+&t?#yAgFhlJl-zg4czQ2^s!r0qu${zy<> z0Rf>oXNx+l>bQ|X$Wo~qpJT=3Nj1fI&mu#l)bWzP6q>%()KJy)n*1my?ggkf)NpXP z0b22Flz!{m{%GuB-Bq4VM~~T9q|d9AHZE+e)A3N)3a=Br*?=HofFSj{D2z0~w=i)% zFd*hSGg47(0WlZ;Q2_%Gb9#E2*_j-=SvUFKnY!7?QkvT0a4DI%JaJmaJb znQIR&-l?t9uHK@T=7vHZuA+lBU5GN~z1+zPsGIboiK<_@tCZh>_#k>mA4RpRzX{_Z ze_k`B5J(0@7bg#d^la{jBv?CM2^H8Z|rEHD#gcY*(&CrBR8%U zhZ;DjpY#keE}f>#JYd=JysT^zoDp-{h6Q;J>9blF3!J-Wy1hG#qWbBHfNbCJ6Cj94 z&opC%SS_`TB}{8uO=voEzF^id8MwAuDT>ohoM05V5Wcv=A-F)zQC0N2{04He<(`_) zD~Ekt5@*w~PLFPfLo@>Pn!^DJe~iHq)i{R}h<~@^>1-C^pARC=3I7&E{HH(%FzN80 zy10JYWk1eK{)3jWQQA=<`CAoL&x6A5$>8i-LC#YpOTOB1`i~+k$9~Km!ySoVe4DR%0we5aId!V@z zAk@q&Au0OFv5*VP#c}@|?Ij~fm3h^Ci6WT<JVUkz=!q1`hhz>TDzK?%e(YAJ_3rN z1P`Z(@OOMv#KjBm=wGlZ@UhU#KJ*Qj8cTT3NpG>p%cZA9>K|hIQe8)gIhZU^Gzc#7 z%jfyAc!M|8N6FrDBcbnky{O*aH%8!YPYO8+d>RljeV(Sp168^{69w1ub|%__G?-Y> zW+g-0%0XdOn-6kFZ$93WA`=KAvTf+d!0-b??aK=ha7wtAcSZ`5F3`mlY{r3+o*b&D zn~@&bsgs%gu~H8JnbCz_c0sI&er6()MOIb?ezAUX&5KWZK%nE>KtshZP~G?#v{3!L z_EJSd9G5!9W8#W*CBEvLIi-_qS?Oi73Ya^kPC=sdf~&HkX_d>=xA-dAq>tEZ_35uZ zq_%LWnOX=nE8^*@e=xE zjkm$Fk`=Zhg-q>hK1w45MjL+FV(xyF^@@d$UXez=8x5j#*h=^{+Pt5WeTYSkn~Us- zPxN${bwGlK=vYOFlkTXWZr1m>?XDpZp(o!AdCadaWKp85CJ|gZ@qTu=bW-sRCcCov zpz?7tzrg7GeF5W1I;I&IZOrmH&^@#~K@T}t#uhWWTB${>>&YFZO)LJ2idAZ~ftBc4 zQOdQg$Y*xbog3BG8@QA`cAGSHJNcW1l*M+x47(SGp9`-eR~K&`)89~ev<5x33%J8G3|43~(` zw8lgM)T-h`!jK#-GleF0wI=M1KAT!>drCb!7X4It4f0sLO@7+WbgPx+#wc&Brb2md zoX#&!eg>e{JWa00*bG3eI;vi4`B{Kk7t?}L&NK6Fs&-%qHuyu+zNMN2Vq`0^Ywz~4 zniB?9JBgch~`sqaY zA@7$5_(xW>2LPim)*}LW*hT_^5||}R%|0?eQI)OrY=b7L*_v0$qbi3ofF6{5DG$`uv!!#_GkrLg?8HMA_D$S~#pvzYIK#-BX7(0=X z_e8l%M<}|MX1Dri1c-J(k7l|9 zdT~y-4tcpr7wB!@TnJ$jk z_ySy#P3p8Hv|5ru`sm#T$C4?>0XnIwApwc!X*2e&+htMIip^z1w&uRzG5N30O$B{D zwx2W*5ak}uX^?gYF0*LFDz*`fE3?jk8OS*Llfz;q$6sajVr+60ZCAD_^@&?-mr}x7 z?U0@J*z=~9MPKZ~G#C|bH6u$~93as3Rr5rvZaqD09lUzBO{eNCxd)GucMU!6>|e9A zz{lnIKPC$P4{H5uAJ}bqk^Abm#NzK^_J>~C&)K*??Bo8U9sDQE{)RBCSNaCCVC;Y| zNdU~&Q~g3^fI%7}NN+&Vp?VvsP$pO#nc0Gw0~x?vCvPg*oRQ8<;ldSa8ky1)yR4U$ zaL8HGwb%sI`Hd7+R0XYUoK%IKf|rkqdht1fiU(w64D-s>X=rmMiKBwbs;sbbQy2n8 za#J(>mN!$znLDM@4sKy~rl-YSOM*9-S9fJ}v|E1(X8>-1A#h-9{7U@{+yr||G_-rL zrj3Z?CiSg=?FMA(A2u_!p}9_(@FMtD7qme|q2%*Yf9cNaVtANr+6})XYTiS-SiN7v ziGxzq_jHngm5(2XZWr?Mo-)JWT}rY2L`jC=qdsrhTbS(vFcTrkJRT9I;QS zV->Y9AD~5^DCMV8eQp_NK8SjfJ#D^{Q#NfDWgs>EdJ&abP)%d{v;~ix*eVjknEwgH zfzOXo=*y+jdH4g3TI&|{8ZnEhc!uDUVg0ZfjIm5kAh9SGhRQiE-uI$-tMp-TMQ7Bi zIL1yb39Q539aM=c?p6|pfWFRCLi0m-uUh7##;jL0hyJP({mm2qNG$&Ic-%jNncDSl z1;n3D*pK!`|2*%%yMe!=6V`^sE&oqaYy|J^fo60Fooa4`Ez3%0tg#iE7)z1H0JE6B z;Ph_=#Hf^w!4jDnl^Df2msw!YItoVo&h8@2Go;<$3W&LoithzPKcIlP^{s%Y0=cx_ zoByCn#`Ud$_(e_={aXQXb(Y?^&UWT(LYQCmd#rImj9>AW0-_)gYy4I~Jm6t{@B!<> zEx``muw2ru&-N_AJ#|<}U5!MmJ-`zoP@Ytc&K)6=kNJhH2450W@S-Ls{m`N(k&)Gc z5BuHm`?^9%$UhzX(9SkjtCs-(Ipwl!dBEo-^dw-_$N z-@i?Ih;WPhe~UG~5D86* zcUPc#6%(pQou=#sNU@+yi(4tCbt}bk|3Hd)-%7DWe3^o7zi(2E!=R}9yA zQ-^YLl|FD%Wh@#g$mWHsPd*Zg{Vv6--a?%?N!Qn2u2g~RK#QAs0#Go|7#r>!+VAlr z!dLf0!PCByY=Q%pm$bmWj0Uw}Jp`l}vU#=%FFYW{n$Nwb_IW5=4e~l4QVt!oFf`FJ zcCZk*6jrmH>YMiB9Zs?Eal>El^z*~ev38?|pd9sjlg3y;d_|s@3`i-~NJ3aCCk}g1 zO%0C{6jWo1M%hHk*b;R`Ke>z{1mrLah~uD^M7KysYY)*Jcq-5(sykYDiER#G9lM|LVsQPZ!`*+O*V->5q+mgR zXm|lrbb-}gKd0#ayNB)P_rK*0{3Q*q|Bra(io#v}4_<*PZw;?oUadj=jg!Cg>cfr3 z(sy2c`IT36g&u1+z`za6RnBbVuTE!IWm3(m=5{GzL@r6ftZJtAc(~L2#gMW&bBCvo zl=;Cs>TKbPyiyFAy}64Djk{ya64(bV?BNN+@}V+qBJlV#H2n)OX^2;C-)a6BjlwfESw z16t5WqY7FPOda;CNM<+l+rd?e2i25G4%}5VMb%85M0o_}wJd`M=G~|eC6*+1&L#6s z?mXI~ett@Pi@ui(m8^q&G#9KxFGLMl`Cn2_Q}%)1e||Ko%qiygsbYQsi1}aG{J`^{EE-4Q|5Q40w+GWc7x}U%9Jb@q&K^t@Og4MNiGH9 z@Y^oKDEmEyjx;7dUO{w3yIAt^S20~ry+>xZk(t!Zu})ftcG+I2*lW67wD@PH=-#TW zyx-icf7clXv@ZiIvc7Mg1Xg7I>vEqhytefFks#VX#6iP)V(hcL9a|q@?%-*+kru~K__-9C~ zfP{kjhNReEk>vDELfs-s{F{UVki_s!LeUjE|AHj;??@^RX;U?|O$nRkzo25h|G^Fq zm*`_|$*)K%03?(fmO?raKORRhfF!&0SiT3$tmVyql3-%CRAPlhGQvA)E^gc2AY<$fU_6pWe{Vk=~}> zN_%*%XOIl%Y*yXzX>Gz1q(=m&Wxlo189*$hZzB0*L6o=}mAC2@7hW z`o|etZm3;$TcEQlnA(ojVGElpdqfNCa1l+++6!~8@;ewdFC)8eX0oJuE|4VgduffE zSz2SEl+A?rCZ9z>j_kIi20&-k;hlo6u#X!)N}(3NltX4??nm(*I#yL(%q|*}{Mupx zEgr^tqM~1>JODCYOnKZ(eJ7waUPZr1+FIGMEZKU%dRnY-iWc(xNLldV&fR$nJn{W; zW;$`4{@^}MTc!7$TTjGXHupAP z0lj7Qe=^wpKPUi>V*MI_|5wWj$X@`9vVLpt`e|AH{!#n+```Em{*smzId}MfNvwC@ zh(-8~Sa-e=>-@Wj`nSYtk-H_>mP>jg=RT76 z>3B@`j*skxpoVA2=SN9?bQ5C_{V?sY$@t>wjuy3qYR7N`Q3`)8`cVSJ@`l|AP2p9f z#(Qt9`Z{8gN8_Zl-VNU}LyHH#RChKCzGZwm+TsG6Sa4^#qCeL9HZ_Q#ur&J*V#&aL zBbLa;7tsGktjt?t;cxzXV!c7lqD}ujv5+&03vY>~_|9HWbdvNkq%5=-lU||eAH-_( zsfRiu4){i_`YObs&0VN(#PaLCG_HX{&t^!vCf+h`L}KtwZ$NvfB-IQ9AE4cYHR=PZ z#YMiNXdxQ(k!puI1y+%e#Q7FB=jA9B3NrX~M|KmHMN9QC+RGL;5$uaMxqD zk*BDB2TFpoU4vb2RV`+cac#o|Yv~%aVckJ6DU8ZDD!J0N`zwD|ME#qMjt;-hEBt*Q z@^2d*fwfsbTebeiFY?c${oxG&p!&;gbZqh^MLX{Dd+p@C}7*ihbp~jDS#yi?OIM#Np6@@ER(U1VPwT-Tn^-6SwZv zZMCn~%>x`}L;cO28prsEeNOqSJH=)f^1a#@ctdtu?aREa_I0km`c~~zyRG&G6@IJs zW!_f%8ts0o_SxzEYqgK_Hd_%z1yuXGvWj{Z@N;sDUT@x3`?4}ivz&&v3z#p*)!-Ulgl>LuFu zH3o%Uzhda&L4JN`4Q%2`J2O0t)tpLNv*jS3uF9OOcK~U~QtKSt!O{5y1TLN4{gwjc~wqeF37F;=A z2e|b1A6ycV{e?>xm;jfwBL8bHWdWJL-*ZV{b~G{be{hL-^H&!NU$ed%uDJ?ab2g$6 z23nvLLKE)Q!rTLjhalwWy?RgPsr)ANLz?tv_%})gb>`~6yHNOsC{FpE^|Mf+1q<`+_AF)X)D_ zDX5?CGNo8p5{B(q_?752OFPy3p3!0Dhc!53)V7W|<1}8M<$9HxlC6}VjvkdNF^BmM zc3Jgg-_xD3DCoY|X))ZxC`sD4yf2AG@WMt4oyO=uik&kzuO?W|=zKX`JNJAg+Ir{D zFVFgwOKrrz*6#k$ZTerVrk|S}f512S2RZWJ(`~w(`!Ea7eQ+ZmZ=*a{{~T>q{(rhn z-_dhiF`jf|oTfZ&4|M#tC@#H#Tv>0ee-G?kF77W&B=3If_`Op`V!M5=t)v3A0S|UY z#=yCOcdnNtBHmB;IY-<~O{;_0nP8lc;rJ@CXcLQrrfhQnUqBV*QFc*nqo zne+`kd^Buh0D7p0G~BpUu*rD&NCEUv(FZ5e01MF_+5b}`{fA9ER6GGWafA1A#p)I?3C>UaeiGXx50{W{yfr=4Ngp zMZiE#Jsk3uOzwf+R&J3u*;Zk9rU({Hn_w1=aF1-&mUO&G)^>WWyXJOTgGk7B>5>)l z-SV{&YOLa=Qc=UowHK2+r5$$2c_q!g`q|Lb(*!%REgOo|FpTG-BWZ^elEz|&{0-;K*(G>d%yKP>fAnv(E4bH-L$i;h~M7?q0#CA4WR2VYW(v9`)dh*|b2scr|1 zC|XY(k7>GI>5R%dqn0Ka*!M_I=og`ar$4t6fs1E+Hfq?%eJ`%92R=+b2hVvU(SsL0 zQrAA2_uz1{8MYDZu>#>ry+~VD)`*2n+KA*Hbbco21+P1L8x>AuL=l_wW!5=KZWZG2 z+ix>h`El}=?e;z$jNQ%cA8%yfci2ZH^1s?rXYlXex%9;X3wE1oy2^4`I>x&Lv4r+AJB6~7GU z6C!i5f;|vBpEm{@o=Li4pByJRp?QsAc=bV9C4S5u7P_Taaw0G73)v+1kdCo=fMT!{ zyiWlE<&B8ywdfyp$15NRp(lL8;~B*W$(J|0^6%ftVW$(gD_X6q8l$*W1Q?*$32Q|l z=q0a2LhHVbaWuKWo)gd*(bcvww@XXoU7rWGksy0frn`P(gH^wsTLBDUGAjlsCLIZ^ z;gXMZfkx)PonY~uVwD-cP>kw3#WL%^Qw-L|Qa8K$L{Cqo9FE2yNB&$dJC6c2YpYO( zXp=UNjE-hI|IPNrR!I`Y{ej{sIGT`>G50LPm(!u5+0yf=KHSACp`syK8!slQsZ7bdXE&u@_4)Kz%j(W^{bA){6L0!)8OiMFHOzuo`1-=c~dzYoGSFWzq@ z(!Qmb;w{DASTJ``A;Z>pfoK7gkvE|fb+fn<7WObKMi;db9Xb^8a%~zE^xy7(Y=OQ{ zR5T#^X3JuL9|!fQ`3)DUWv{%%cEGTlP~`EbnmLN@h_rFfagvUHkL0*^5-NDguapQp z?K|Pfo-_7&qsAKI*!3Jd>vcf~o_EG~dNTKk`i0GqC0suMFdnqDC7n0)2i;!CwUEvj z|7l1^f<5Z%F-wjaGv3TRCt!gV4y^qaOpiDxOzEIs#bD%YZ{>RJ*By&JBrox+&3Y2m z{+(wu3=W6A4;d_u5X2;Ej`&oK1A6{s(^NbN2KQGH_P_X2z^r`m0+GKzhc8s=Z}>Vpzr(I-fO$R|tpe4#is6ii z2jB}4=?uda{;AHC3OExUqPP8aP0sx;Tk||0w#pceCqbp4y4Qz<@_295kh5qtdS=!I zUS@i@L?=>NSrQ-r?J*5aCg`UT0mvWRlRRRF!vlg)neigOAb=~9U5nm_YieA-vsL`W zhCPzaf>hIrEmCJbN)nu`76GR zKGi3~OWT2<5$|KH3n0h?11#e0GBdN_7F*vyrjbIQM!1!2b5pv6nr1t#(rIa{q*?@PyLd`=6I?oXLyc9oL|kuB zK1ZsUTiBeqNt?epQyEk>ZiZ!?#Q@>ll%EvY%n_^UbXza|a#OVq3|j(5z@r$G*Ed14 zc7vNSM5Dnin2HAAR$Tp8;5IyaS_}{wOg=_Ck-m~y2W65{K?}{7slqOFQ9_n3B*@i4 zBYPJxIuPa5;GmD2lD4?->827(qY%1San~!7?SKJMp^2j*X(1Hd!Ixa9$4N?@J(44e z=BVItKX)SVgl}dI+wkYg>(XA+sOR7*uTDB}Q!lpl6I9fw2;V=MRfzi_Dl^!}V zbvjPgfZGFAr@?I@qNuNjEs^NWcw?QNfGs6Bz?Kr27{5yB%siv>4Wt@0PTs;kBgyU3 zd!OugGETcW*J{7Mu-j{#B{ttLCh@=8Q)bw`I_wwv6J1j~|IVudX59iK0)KW){ZMc8 z&%=Jdfxnq-J`1%-D@x?|g*+x;DA!vS{f>vn%hgxW5XsMjcmM z@KkWEKSctV;9e=PHNJXdH0uYsx&J(b6BhXAu~-B25>NIacC%X7QM zJ%?5IbjoO7cu${*8}3W)ZAI$-dA>Zr54A=fm~~rH9uN=#_6i7uKR}r9L}5CQ|B}pe zzNqn4$}2JiQ7n}FW3?FW@;g=NGXiLJY~htawOFqe7kr+z6>elL&6Q}_23tss4R-Nt z?Amcezk`c91*Gpy1?1bOpoIZ&~FYnIR5+%c_(}a-d@> z7wI05p=d?Ymy0O{Se2AWwwYU<=#7 z=5M_-QlqeeO^*Pp^5vme3F{jPpKdWW;|bF;wP3$BU~0wGbzo`(+g3BRgFyWO?Idpi zR-q(B7dF!50jxsh16ai}SiRRnv6{`&_-wHnqvsw8p+(<|Dg%ptbg;5{gAn`nQByE6 zoyCBNgx=!;N{O73;Rl@8mP7CE(XEf%vqhzv(2crY8wxMuJ4KvF4acPz>QFF4}Y2DW;+UQ0m&4^hncy;WxgOx|x4(hEpra*6%Kcvg>a!%zM_43WhpC>bx!uJ&91F6YeHZh}_9W!d zc88?HP52eTS#d`;p{=o!8)N_6EA?Zaxuz~<#@MQsL(IV}oLOSqbFV$}&NzlqYsV!d z$>3SO%YC6)=ej$OATFp_I%>`c(B<*g_nz7)tAGV@z^+JdZ62tgXnGRz6Ot~?o7(dqXZ-zQ)2L~B^V@MYS;TJWtOeuU5eX3(gK9uks?hHxG&Bf?^)-}oO|Ye znl%?bW%2FZAO63!_p|r&BwvY-eWbLVp76nLh%?Nl%_<`RJtSwGSZun$4O@O3oT(DF zvhd(3^(j`2Y$sQ@ZoA?joue0^iS)KM$vk%Eo z@yag?juox4*=(Dqq??ON$f_^abbyDgc5VTf1(*fa@@UfnVIyI-#eG+EY|w*(l{O_K z((ZPpzEqH-F%A)`@JW5Rp@2weN<5>@@?? zOZRh%M9Q1E2WB>A7(I>4X3<^h=mwSrU~mX`wSBgh0jYVqUJEeBjRt`~{^jeNNe5@#oK}aonFZMYt;Qvb!GlXE2~YGx$0KBp&)~NLw@d&NXztH^{=Noslk^Fi5(MX9D&G7s&{^$rm40`r8-;GbL)m6}sAnVA; zJZwOeqTgZl;Os%P}WaV_Wg9u>AyrRQ!~6BdNS zT1D$I(e2zRq^ORa$wbLE1+W4uRt9_^(XH;kEs3R?QE+HiXS>hRqavhu@$)s!Ky;0v z=!)LwJGd~k>dmkYhXEhY2&Z9dc!!gTeGTU;B~l82#-InZ6gg*X73Hy#Z3{fDMyem$RwTjd`w2+n9w+n@GBaK|(I_l$Hk6K|}rjB@HXv#pO+Ho@<=Bi2}Nu)BY%t315X)#!iFJ-w%)=D7vAALXe! z4CC7F9~0{H+$E3DJydZ{;o8D4hIM)E&(uIJ@7?TLRodNV?sGX@=jyzCa0E^Tk!jTu zvab1%>-^{V15^FB{PC~q+0Ov36ZP!pTX^#0|3d`;;{DaGy7XDaej}dJuMW9*i2J6`n?~$L5CLhbBMNqM_oPKd6-tZ{kI@6o9 z=U}`3=!m%cuF7vxE-r9e>ifyX#WKCSV-7XEDK~2^M|lMs2adbT_G&sVgbnI68#w2r zee;c83g$d5Ppst^O?DNQHkKRyVXT{cZx$F&3qZ+@-_NI0s9PaC5+(YQGiZQ_N!N@F zKt0PXS+veMByJE~$}Ot4rUM$j(40J~1)$+8-Y}#E8a^RgR153Abz%FSwM`M0v=V6e zIJNBxKhir07Yy+h+vSdGJhv^Ieq-~ceEtq;Z3XDP4PE70iA^_XlLl*tSu_4+wHi6e zhOFN2$7R|K@(S@gydLk1Nof!dLzZ-L~&Ji=jGw% zWwQ$#E#TKW;?@MIiG@(1B48}J{et&vyVL+iyi?A(qzI!pkSpG*3|5p<)xdd3b}ou^ zIP^jYI2<0RGUOF@-@bSMZa>XM;b#L^j!2va%>Zrfv;LB!^st%cw4=$*%Rqd|qssPy zHr$h2!rdb-pQ*b?Prc?SYiAf;kx~mBKufBJP6^BOIdRjJPtwDU)q#n-VLA?rLR~bkw?-Q7f*?E*ot- z9gbhASsUb;2tu72GEkL^BW`@zq_ZM4i)Mw)VcWnDMYu=t@;Wf){xH;D&rI{vO z=wKktvX}MzrR{VO3@n{7+ zd`JjT+d4<~cS0xlBIk_9Fi$s&h4cCFV+XL^?8Yt&6MVHV5kvYM7U<^YxP0g+T4;TH zW@hk?v;rS~T!xLi(_9Ir4fwR+IN?fVbr-PK__8$tBM&@{_c^!cIO$0})65g(&9TPw zL_AsgY=H03u32Q>MKvDBfYIPIsKoU_vxgcYuciaFHJl!Xl9Obei%1%spMDHBV~Kx5 zVL)*{D(BlS_lqdU)(3XxO2M;cU{?cN2yD_RIS11!;8GTn zNeUQd_J=Ozmj|{jAqzt(mkaVKwU<#9oS$s+hy~<@iYVl?1&ircuCAa5&2v^tUK5N9 zgGLNDSIWi>&()R_DrZ!wk{t}pHHT1dbyK;m|;~^7^r1?Iwn_}g8zh=XIsF@*0H|r zM6{%bJ#sEHs|WmaZ{s8PyDDmu+*hePXufxpK=4xSP0T9}8_oAR6dEy?brm*xRCPVK z$XFt(wnAYrJ^a|8U(Y93)A~7;{9RG*AJ5GGNs&7_GyD1Mo&5NlRRG}FU&_?b1Vusr zT{s)$Yf4N>3Rd*ED~=@&r{;4Sk$m>BO`iF#GzL`E*%70NC$_sQb-jmN2iK=lQM4Er zeFf|#%;0D>EN++CM14zUH755)LnaLu?bI@`1zo|R;}Ynj4Q1cDcc7ICaHcj!=}GR zbcIKr2Z2E3*bmU?J3vBmEHwKi0l=}W%*Q0r3&5EfD@Z3LBilOL`1K-iX7)O}crmxw zE0i&}?4$L~Jj}M??flw`OTgC1o*=L_VlD`5jr6Pi!V-?9W3c30vJ+shIOkwKYT!eyOb`X`v9S zC1lW9driibSn!dEZRkloM3wz%7=wHwqLK1mEFj9kM^J8DHo3F}{0z0xEoZg$3tK6G zsU-A5sm{nJa?x{=Z9)Sc>oD0P9JW)kcNt!XArS_mO=KK4yRrp1#Cp}3^RT^I3A||4 z+xI-TdJTmR^af~5lh6-u>MA;XhQwVn7=Gxy0xa!ANS#L>l}4aDN6l2v?Tlz}9Sro- z9SC=idC(b`jWBTM>*MKJ#>&Q{$gXwc6?DJAjiWUqUDX|N0bkW)iSnI0oFlI9%`w{< z4}4;I%~{xj^u{2TSy4LrO&k-+^39e0?TA%210#2>26*w4kQY1JdS5%i#GaeI@+O`& z178!Wwrw>{^v1>@#9q79;ksVE)0b=DdyAjiz4kLwxQ_NW2Vh4ByK~5+!{3kSswKU& z=1Uu%O3bHKM`^g`ce*{5RFVF-4gP;)fe1)$fL-s&_1WL-6+ii$KdJ!0x4%>br3s3h z7U0{s&jisw^6km;559r#X@nchnb-TtPkpz&yJ^{zVP}1qD7&T8@}95q&6T|k+gP+V zzA4Fl=hNr+_x6n`Dod6p1jCv&s_9j}5WS5{b4cEo)g&6E2KZLxv>85Be+U!ZHIms~ z8){~_;61Lex4B4C*u2Z>>G5qwxWClfTgABtuU^dMy>qZ*-~*xNH}(ee@5DJ$2nKWb z6UvpbJg2@w&gEZ=B1HtU-P|1sI&)o%+Y5~#p9~?2nAZy#Mg{X&v%KM-QYO_199Iz= zlhq4Cd@g$zCEW8tFG`K++YnT&DR!C=Bw;o6@+zt8bo4KGT4P_)n;S$2oTh)C9%EP% zgN=Q3r*+21is#f|{65+M;rDEj*&^xGXvkb5_=_td#EVpLKJ~dE2?!J*;_xFldH$^& zNS5-_Bh{Aj(N_bO zkWrxxO9k)tD3?*ufojWzuUW$`6jVr5Ubx+LD40`RTw$1(m1TBs`A(cY?n9=itGj?f zmqx{sQ7i8v;3MB81hXdW69Q=vPirg3Zx4qdtM`Qzx-8kwZRf$kH(+*jP|?|Puu zFCi~gVrHHaA+2U}MB=DsX`=}5dS*g>#a9|pL=Y4V#6z}!}z>0Jw42?9JhLPBMt}$U`UU_G4H5n zyOZ8JUf<>*ublRlOIHv7)C}^0_s=Y^f7R81HIiT55>IrsKYIL5{{OeEz+X*Q`@yt} zZmDa&b*j{C8dRrzCqMeJ?08`AO6ax)2(LPX%N5Qh#0q(ruw43T*^{n2FwH((WF4EX zUgO1r+q4}FGu?DsVXph*jR-jhefh3%jhiG1zvc!Ng@%V9mse016n< zlN|Dn`}HttMf+1iWs{ySHr{y~K>8ro)SBk0%iADIy}{_rseKr{%;;k~$ZkaFT0R9;>{f zrkHp`wmv`O^ML2n-Iw4u26S=OHyg|o7(SEDsULKd&qg`TX3Zsg9FxpbWOFX{Ig|B# ziX3C=G1HDo#tv-y2a>M60Fo$&E%jdkv&U4yVv<)3 z#sxd(R%S+f1TG2|j+ka9F^-vuk>o6>O%TP<%~GG$iJ4Dx&qvf}O19H2u=$R;X%)a7 zkk8SXVa^rAE1i|f)kmBPtMyC9+pASX5gsbKqA4CdokCcT&03Rdo?DzIQJ#&%F#LhC ziT9tj1|gyRZ9C+j6ttiBY9|WX-}0e8`KW(Wfxnu9W`ZK4`F_zlwcx)gw&^qZG2C0p zm_6=zAJP%nhy0+if*Oaqtyn4kmQ?N zmi_Ete*Nns>6bsWJ5z2knb4loSX>xW}exl)UP8xy` zC#YY~y7vO*`NDB2Ijxg%DY5U44WOXCVX7@5cpz0Zg#xpWf8%@LU-^E+F!!Q!oyw3a2+622s3_2inz)ip#uOX(h;tSD z4P)0yc-0bOmc5ltt~-V6kgj#zH#tw)9;pOagPa9bE)^k--AhWRXyD5v3mx)Hl}9uR zogY8hJFM3;hkI=JimtA05MP8GZ1(BH4z>m@kO$i%|3RF}5n#zE4@{9y{!S`zQh}2S UoK)bX0w)zXslZ7E{;w7IFBB{E@c;k- literal 0 HcmV?d00001 diff --git a/libs/portable/src/ui.rs b/libs/portable/src/ui.rs new file mode 100644 index 000000000..0a015a248 --- /dev/null +++ b/libs/portable/src/ui.rs @@ -0,0 +1,232 @@ +use native_windows_gui as nwg; +use nwg::NativeUi; +use std::cell::RefCell; + +const GIF_DATA: &[u8] = include_bytes!("./res/spin.gif"); +const LABEL_DATA: &[u8] = include_bytes!("./res/label.png"); +const GIF_SIZE: i32 = 32; +const BG_COLOR: [u8; 3] = [90, 90, 120]; +const BORDER_COLOR: [u8; 3] = [40, 40, 40]; +const GIF_DELAY: u64 = 30; + +#[derive(Default)] +pub struct BasicApp { + window: nwg::Window, + + border_image: nwg::ImageFrame, + bg_image: nwg::ImageFrame, + gif_image: nwg::ImageFrame, + label_image: nwg::ImageFrame, + + border_layout: nwg::GridLayout, + bg_layout: nwg::GridLayout, + inner_layout: nwg::GridLayout, + + timer: nwg::AnimationTimer, + decoder: nwg::ImageDecoder, + gif_index: RefCell, + gif_images: RefCell>, +} + +impl BasicApp { + fn exit(&self) { + self.timer.stop(); + nwg::stop_thread_dispatch(); + } + + fn load_gif(&self) -> Result<(), nwg::NwgError> { + let image_source = self.decoder.from_stream(GIF_DATA)?; + for frame_index in 0..image_source.frame_count() { + let image_data = image_source.frame(frame_index)?; + let image_data = self + .decoder + .resize_image(&image_data, [GIF_SIZE as u32, GIF_SIZE as u32])?; + let bmp = image_data.as_bitmap()?; + self.gif_images.borrow_mut().push(bmp); + } + Ok(()) + } + + fn update_gif(&self) -> Result<(), nwg::NwgError> { + let images = self.gif_images.borrow(); + if images.len() == 0 { + return Err(nwg::NwgError::ImageDecoderError( + -1, + "no gif images".to_string(), + )); + } + let image_index = *self.gif_index.borrow() % images.len(); + self.gif_image.set_bitmap(Some(&images[image_index])); + *self.gif_index.borrow_mut() = (image_index + 1) % images.len(); + Ok(()) + } + + fn start_timer(&self) { + self.timer.start(); + } +} + +mod basic_app_ui { + use super::*; + use native_windows_gui::{self as nwg, Bitmap}; + use nwg::{Event, GridLayoutItem}; + use std::cell::RefCell; + use std::ops::Deref; + use std::rc::Rc; + + pub struct BasicAppUi { + inner: Rc, + default_handler: RefCell>, + } + + impl nwg::NativeUi for BasicApp { + fn build_ui(mut data: BasicApp) -> Result { + data.decoder = nwg::ImageDecoder::new()?; + let col_cnt: i32 = 7; + let row_cnt: i32 = 3; + let border_width: i32 = 1; + let window_size = ( + GIF_SIZE * col_cnt + 2 * border_width, + GIF_SIZE * row_cnt + 2 * border_width, + ); + + // Controls + nwg::Window::builder() + .flags(nwg::WindowFlags::POPUP | nwg::WindowFlags::VISIBLE) + .size(window_size) + .center(true) + .build(&mut data.window)?; + + nwg::ImageFrame::builder() + .parent(&data.window) + .size(window_size) + .background_color(Some(BORDER_COLOR)) + .build(&mut data.border_image)?; + + nwg::ImageFrame::builder() + .parent(&data.border_image) + .size((row_cnt * GIF_SIZE, col_cnt * GIF_SIZE)) + .background_color(Some(BG_COLOR)) + .build(&mut data.bg_image)?; + + nwg::ImageFrame::builder() + .parent(&data.bg_image) + .size((GIF_SIZE, GIF_SIZE)) + .background_color(Some(BG_COLOR)) + .build(&mut data.gif_image)?; + + nwg::ImageFrame::builder() + .parent(&data.bg_image) + .background_color(Some(BG_COLOR)) + .bitmap(Some(&Bitmap::from_bin(LABEL_DATA)?)) + .build(&mut data.label_image)?; + + nwg::AnimationTimer::builder() + .parent(&data.window) + .interval(std::time::Duration::from_millis(GIF_DELAY)) + .build(&mut data.timer)?; + + // Wrap-up + let ui = BasicAppUi { + inner: Rc::new(data), + default_handler: Default::default(), + }; + + // Layouts + nwg::GridLayout::builder() + .parent(&ui.window) + .spacing(0) + .margin([0, 0, 0, 0]) + .max_column(Some(1)) + .max_row(Some(1)) + .child_item(GridLayoutItem::new(&ui.border_image, 0, 0, 1, 1)) + .build(&ui.border_layout)?; + + nwg::GridLayout::builder() + .parent(&ui.border_image) + .spacing(0) + .margin([ + border_width as _, + border_width as _, + border_width as _, + border_width as _, + ]) + .max_column(Some(1)) + .max_row(Some(1)) + .child_item(GridLayoutItem::new(&ui.bg_image, 0, 0, 1, 1)) + .build(&ui.bg_layout)?; + + nwg::GridLayout::builder() + .parent(&ui.bg_image) + .spacing(0) + .margin([0, 0, 0, 0]) + .max_column(Some(col_cnt as _)) + .max_row(Some(row_cnt as _)) + .child_item(GridLayoutItem::new(&ui.gif_image, 2, 1, 1, 1)) + .child_item(GridLayoutItem::new(&ui.label_image, 3, 1, 3, 1)) + .build(&ui.inner_layout)?; + + // Events + let evt_ui = Rc::downgrade(&ui.inner); + let handle_events = move |evt, _evt_data, _handle| { + if let Some(evt_ui) = evt_ui.upgrade().as_mut() { + match evt { + Event::OnWindowClose => { + evt_ui.exit(); + } + Event::OnTimerTick => { + if let Err(e) = evt_ui.update_gif() { + eprintln!("{:?}", e); + } + } + _ => {} + } + } + }; + + ui.default_handler + .borrow_mut() + .push(nwg::full_bind_event_handler( + &ui.window.handle, + handle_events, + )); + + return Ok(ui); + } + } + + impl Drop for BasicAppUi { + /// To make sure that everything is freed without issues, the default handler must be unbound. + fn drop(&mut self) { + let mut handlers = self.default_handler.borrow_mut(); + for handler in handlers.drain(0..) { + nwg::unbind_event_handler(&handler); + } + } + } + + impl Deref for BasicAppUi { + type Target = BasicApp; + + fn deref(&self) -> &BasicApp { + &self.inner + } + } +} + +fn ui() -> Result<(), nwg::NwgError> { + nwg::init()?; + let app = BasicApp::build_ui(Default::default())?; + app.load_gif()?; + app.start_timer(); + nwg::dispatch_thread_events(); + Ok(()) +} + +pub fn setup() { + std::thread::spawn(move || { + if let Err(e) = ui() { + eprintln!("{:?}", e); + } + }); +}