From a04b903e96e463086f6f1abd5ad6a84f4917312b Mon Sep 17 00:00:00 2001 From: lenn Date: Tue, 7 Jul 2026 09:43:25 +0800 Subject: [PATCH] Integrate hand fingertip force overlays --- Cargo.lock | 41 ++++++ Cargo.toml | 3 +- build.rs | 8 ++ res/128x128@2x.png | Bin 0 -> 29193 bytes res/icon.ico | Bin 0 -> 42344 bytes src/app.rs | 173 ++++++++++++++++++++++---- src/force.rs | 39 +++++- src/main.rs | 5 +- src/render.rs | 19 +-- src/ui.rs | 267 +++++++++++++++++++++++----------------- static/wgsl/shader.wgsl | 18 +-- 11 files changed, 409 insertions(+), 164 deletions(-) create mode 100644 res/128x128@2x.png create mode 100644 res/icon.ico diff --git a/Cargo.lock b/Cargo.lock index a8e3dc1..0cdee20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1320,6 +1320,7 @@ dependencies = [ "log", "serialport", "tobj", + "winresource", ] [[package]] @@ -3645,6 +3646,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serialport" version = "4.9.0" @@ -3989,6 +3999,21 @@ dependencies = [ "ahash", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -4019,6 +4044,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tracing" version = "0.1.44" @@ -5017,6 +5048,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml", + "version_check", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 34b2ff2..9f9ee7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,11 @@ log = "0.4.29" tobj = "4.0.4" gltf = "1.4.1" + [build-dependencies] anyhow = "1.0.102" fs_extra = "1.3.0" - +winresource = "0.1.31" [[bin]] name = "ESkinPlayer" diff --git a/build.rs b/build.rs index d16198d..f3a1433 100644 --- a/build.rs +++ b/build.rs @@ -27,6 +27,14 @@ fn main() -> Result<()> { copy_items(&items, &resource_dir, ©_options)?; } + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + let mut resource = winresource::WindowsResource::new(); + + resource.set_icon("res/icon.ico"); + + resource.compile().expect("compile windows icon failed"); + } + println!("cargo:rustc-env=RESOURCE_DIR={}", resource_dir.display()); Ok(()) diff --git a/res/128x128@2x.png b/res/128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..257a22b35eec4a89510faac7813a7f7b66c642b2 GIT binary patch literal 29193 zcmV(;K-<5GP)7&#SuKZ)$3Jua@qfWJ;UqbXQlsdhgx+-gB#z;i%W^ z4j(SRYVz5dHETAnR4Ti6b#?8-pU>mhT>f|#zb5i)0>4}uJ6d*lo=3a}h5a_m_3#@U zS;w(~A9auwzgF{W3D4yt{Ca3`aPZ%?TJ7!$6DB;Z&2xocb=P%0+nYFf2>;nGoc-gXL*UX$bb9Gim;8+kp zU)Kd!qLnLG&gkv!J%;~%A`|@F2+rjN*LG}~%-AtH7=lRmVVh};VU#lU+RF)R(yR?U z!nAqz^Uptj{_NSamxgJz5C#CwB2s_dx^>4g(Vxz*IU#~~vuG80He}+1@n&4#RZtEz0-=zqjE{z1ZgqBv5k7PRP%)(I+a96{@!hY776(pSH?+*coS)c2#s%!wO&cn#7R zVGH-02;d6-F)%Q271Qd;Q>RW{5HJDx-i`sl-p60q6aD@DhgYlBA2I`&UHA11YNb;z zOzdTBurZsr{SvqxW3(aMIL6!|^HdKs1DE{-i`g{z2%G7b=yYt%0Q4U*M>&b5{*S~5 z!LMpwiIGImd5*Yij}i9RkyW!v(s(+J$pw48L8ELIoZQ{r{o^Di;HD7&=^F9ZvJig~ zhsA)4B7XfI`hAL6prXe(c68!0`R1JV14q?7J z|L`Q({FrcP%9aVA(=M4`xMwPgTF!XU|Ux`F3gG-#KjcZb6# z5M}~SIL;4mKdTNmz~L9D5|ws^@N_sy!_jvd7kh^gPGh&8e?!J_t3iTkz+ng(t6 z_4OUA)4UT%e-G)s+8)K3`ooST4_tH|WJ76Dy zrfZ;aF5o*xv;bAvhpIld`%f%0mp}Z#t(bta?$OD>1Kh|CtG#c<;UUP6X{M zNgtH-&MiCMA5Nwy(g!da)K}Bb+u_d`J2r>|v|UF9@4R{Q4%9ON57SByN1f+5E_JLGv}5Qi&m07yQ(;r5M74erDV=%lQ$q{5`A7<7`Y zXzN6>sXR+_lG$h~t(xaD>qa(Ub3h`0`!els<^slx|GmX%;eK(G*pAFQQ$jQun@J^_ ziNh($>C|rG87*c8p#gYhg}urw$|*ZBrQO^<4YImyhhCO@=h-7^{)9Bm#+C_;)p7Vobu=UG ztj~*RVpE$+NYj+$PI=BK1|K!%v3iCA=HE-0xvXK9a0+Y@uukiXA0V#{-A*Z&)xVGy z9k5A{j@nb}Bd#R1GB3F3%@-~*X>}q_t8fSMNW4xmsZ8&Y6cQ}E7N8w-Z(G&5-e~}> zCXAvGch)Dk=zMm&pY&BW%CD&gV0!H|Fx9-F)G_H_A_GY36PGufvmq^<^|E-u9IAOD zRoa0|_&mS~-g`m5w!c7v}%}pklFz!){c#1oAlxZ61vX0T=k#_Y*%%7 z!{$ZHSJIP9mecbq2k4gjAERfMzDUik5RPvrsOz}ucNHe=X%o8XCEIUJv!`!F6UVu< z&1TbS+PEI=vUPR88Z!ik&-a`Pp$V-M{IBSw^0c&X8;4-HigH6{1j?u5wa}xH)R86& z>aIn;%$23!hs>k1Se29?;(^ z)a>XUH8s*n8k}Wlkgq>Tz03gSZM_N2nm(EK*kKFWahn;m$K2U8f9A#-Kd7;Wz$`?K zBkH+Eng&GxqvY{NWXWh<-NY==`_^*Iun1j;*;PD{p;xO zw?9gkU4IY#w**N_@vK4_y^#rsJR~|xdPvK4 z1I!Tkj7m*`Mo-_Uhh92&ChfQT*7Ul)wx^eDyD6)Z{y4cUH34Ya?;!6r#~-LGHrN1p zl8DlT4Ot|T>DOG}n813=07id)fH$l@3j{d?!W^nmcUMJy_xN)!(66q#gD$%MZo21* zl}w~e@I5``^!ED7I7@U-neKJpR!{T0C=Fo5t(A7+`wM;Tpw45Ub^UzdA(}LS=#@Kf zPVYQmS2}3l9ch!v<9$Yeb66b!zY#~I>xjpoxC+5E|594kXk1>w`l3~K6a%pB|27(% zJ?0JfjeJHW^J%D747*44{GX~ZeY`UneB09 zWkC85S3vM#z!3O5)-vjKFoV^~T$s1{bUN~YdGxM>_n`T+rU&C5BG2{pgNow?<4R69 zH(6_&=#oilyD0-$-@4Tujg|Kd=92m8{kMn|1#QVml^c64<16V+hvaL%V5ss zx|QI(e2z9-!D5p~5J&Xu)1K@&22ixU)ybO0@QsB;9wr+&+1PGo)K+?mWB8x?&V_X0 z^$QuhWg53`_mtEpr&>sdTN7J1odJwA5nvubJE%fss8?vHub-y$te_7au{WJ^>>;!f zJ2C4+tT9xp%@~J)Gp>k+tyye+6H}qhjXyg(3~0h#H#GF0VH;6NT9%FMysOvNvYI@O z?tOAGz5DcE)7_5`Q1?bt2wJtL)o?iuwS)oK;rj(d0w&=21BQ8k##;2`@3W={3!%ugRq zN4#QZKat6CRaAj;gb0P`{OdVZ44@1qIc5S_PTGct`xoTwJH9K7`?f2!|K5d5=b^5=u(XVo~vV);b?fB4@A*?;7w%(*k7eT3KW8rEYZsS{`_o=PCXy^yIq zv~GwdyQ}Do55I;!_?FjjII*s53Y6x-9f5u_jXxLZp*`F1s(*f6BXDOJfj3G8hL;1zruZhC zadpK2TooOQCb18nqoz0Cg$Lhlh7s${Yy|m# z1UQIkeLqgEs3c^^8)OF{YabIgrSJa!E?Tp;M&JGP5!CAp&xOc~DUsCcAPY=d7>cfw z7(rHnS8b)_x0$e_QF%KB#^A>MyUNEB?KRs#9mMh z>YAhu{_S`T0&{}ef;uM{!QEd2LOe_kvEM74T3qqdj@dq8e&njpeWum4*`d0da6EN4 zPtF}wxdUBOrqkJfyq7bby6CKrz0J=)jCg9(y_19@q!VwLh;7iWIytIjl07@D=VTTW zz$CU>1P)*k&Nb8cauGcc%;rprdOTHQ-^D8LjrT9013&#c>hmTmbKevDIv4P%*xjyA zRIcae2L@8Rwidbk{d~Ux+HB*AG?5dFCXVl-Ia|-9@jX@QXBFhpMN4VLs;#L)2HGLsIX>j_48~B96+ZW z@fsQ&gg?X`;qx$!>6?U|TY)B2hK5k|eyo-S3~B3hDLcWUQ#uQaid$m>hgW=viReo| zyO#R;U8;_ERE<3{`Ur_K`-Q6^G;`qiDmNS!B&96Zo>wOEJpgSL0L^s~|INfyrB3izZ@d0|LP=z%E zXfck`E+~jpI4b-ns*d;yn*M|)+>l~R{DnJy6FTi@f2Wz#Hlkx+yE|7E4k;$1n;0gz z5T+^TMf69AV>)G`FBi3sGiv}QM4Fa4RTTE0ZAxgn7(tXNG8n|bFa5_IblB-vkjvpd zkF$I{#|@;VYuT{b8-Biu@dnS!T6c|;aeBH2=`H(iM#mn!7suo0(&XOm5mykV z6B(lii@EiYh4kyIZ=s8BxSy6R9VB<$G^H6prT5d3tHVJ{JPM)>;SO|uQIYZqIDVDk zwC+B-_>80JRXc92vLU;wU71>wx3W=)sq?WDO@~YjAdT~-sbN_%zD6qph+}rBq8ni9 zIOLR{(_imcMwRisN-Jn&$t%0sk^uyO9+Hwe!6nbr4}bR;u1dU@R6yS~z`jY~7c_}h zP-)~Wj93p}4Pcfx$O%IZ4KTLBPFr-*mES#qw%TY?BSI1dHx`#jRy2HAmNsggK=4Lp zZ=NuqO^IbIz!^;>LOWV@jdX_kgmnE~kI+G%`7I4rr}==L0?nlr2bssggQ6g3Ncj&c z6-QO)Z9Hy}KKq`R(MfN6HI0Ye&gy!NPp&A!cN%KBH>`rl)6o9HxS_t|68cG{;xmG) zZhMeU{n4d#=M#)oFkvzc*5MPVYk($BkTc+oU|K%0xa5~L)s#Hx ze=kWC@`9>^oG8cbiWHT>81i1v9gThaOtIoR1urnV#-Helf8Ncw8a`;ohKD0)X^2J|FkWP2z-K^S19=#TLX}BV>EgdXNWZ%F zHfB`JOxP@`t7=D&oDOSM-|ly|n-Qt*M`@@;s>S0fgA_P-LF92hO0YZ1v4H znn)LX^Empz8@H!*D^>xCj4cu;1U#0shG01r75ap!F;tyGUpeQm^!Tz>?6j;jLyt~2 zsoO~ctCMn7+#+N9`ef@b8&AJ2N3-#?maQF_*E~3M*F#UxLry=+hXIqH!8Z{q{=ZqXsBlk6RHefl$7mg`n0TWEr+^riF7Kl?tNN8Ht$@57 zw-G{19{7gF#u_KbuvyAySJ@$%y)RxnkAA1MwzST^1MmOUGuITTlXP#S0FECvAb1PTV zg2m6%lZ&3GwS7Z0eq4p-@VRqYQ`l_A6xx>Y|0nnKk{=EnQW%7ihU`gpV88?#^Qid? zSW{QOo7mG$-~Zf^bm%9~rA6x)C78_;g)mIfj=*q|1o{jprpC-;`SLpb;G%!hZ$J0W z0D8oUEf`8`E;0skOW9I^houg7Vk|Sow#qv8QWBDazYjnCqMv4q(4FUIyppdC0Bdh> z%?djGz5CN^_nuGJ-}@Nd_~6s@p9dDwQ%hFRBTp`)6{`pQ^HFS5bAkXpUJo#V4$%b8 z{+qw$6gp&|xeV&LFKst#V@1qR4B$Ad2Xb84RODQH;u9F4J+_%mpFC+*B$rLTvqOI7NRAZ9d(lFMr@5 zI_3@gD7CvwQNA01eI%@>r;?A=zhM@v;4tKyKKc{-*8;Bc>#Zul6LbGfT@A3xC`dzn zYv{N)Y)`*9;(2U7XnqdpFm~mCiHaQ1Ulhcf21$|{7UMs_>iNjlsBzP&+!!WcSZRU>I?(c zO~)U)FPA#55or^`6Y!GfN%=MSZ|?-U>?VFTu1%;YUxP|ZQcX+98$MTQE#%A0LTyJV zFv{VXw1+4u3J`UYN%d6`pO?vLZ7RK5rNy`!(RhxAv`b0qt2Zou-pr+AqNuSSfSYo- zPeUmIXjrBM3z9zF4(&tt3kJ1!U(h+q? zFrfc#GHE;=y6+r@318Dl={29Gz$u8^c1@y7IXP%Kn-$%NEYydGO_P$s()L%GQrR|_ zB@1L(xXd}~eOx_`OVpWl%|$GV4FHKL1Iyc~SgwkOn`o`&s(kEPpxWm}t7(Q3CH2-< z-@U|y963kN`>V14pxV_-igK%pfl$A^JZgZG^BUT-tM0?BQgDF}s{WR(S*uEaldc_YK6A4 z771uzV{2z2Iuj;Rw#^sCVQt3khp?C^w%(NH&z{TDvSFk&0$ZL1Q-Y-lQtL1ih*i}V4QhSkcl*R!;SZg&fBYjraK0UgiMbiZA; z;IP|TKM@Ex`UQYhBsqZW=-6`@AL5NG#%fR zaeY5PcP~Bk#EbOU(iIIerQT+TUn`h`jkcQP$dAg0acyo`cA<@FHecDH91|7GmV-xK z^WazRK)vH?Dx4MNq-+0%b^s>C9IHyJIjxu?AUzVtsgRHSx0}zL;wL>>KT9({f+nOu zPWsn9k2f2X#Gqpa zeeF6GJ9p9&Z4%vOw%?_@;2aeDgfahZ8E3%Q=4?Kr5v^!64zb*-DvH1jM(=;(nc&)W z{gSEsBhFLaY1M#nV>8)bN&d6cJb*k&Hd(_LS&?7Ml8#aKy=gefwEWpO18Ovp)$3yq z+KX-D)!Ht0j6yz*>pE_#Q!sOPV5r%SpR3T7pvEh{=;I>tEr z7{?I|fVs!KsWj9-pz7fzFyk0+Q1d?E_IVyP&Ke||I&C8DwC!g04KWeE3%=_gQb`KU ztOv(lLs5|d4oyDwY#+VIQ3>%=*kpiqO1XHMriRTPcH8M-OA2k+0Ecvv=QfEc4g1`2 zRk6+F-X8kIk^6BaAJ@;jbuAmvYZl5T#@?b*Xqzv6$mZ)Bvu%oW zh($ZvvP?vg3zt``Fzp?a$Cch+n|H;Xf(D9)DGhA%AUT@uHRO3Q3A1y`Z=s-`>q?=PKj zAZ;=c41v1>IUaci)|ubkr`hR;oF+O@kSRmtnEL{phTh>RAkLwoI&HkMOGmwGFMqSR z!yAMqp+7Y{1pB{PyUy1rJVTSEF?X)4akB;jS6IG!o$juTb=aomXmlDvPZkvGJmt@B zr^VWn5`H>mtWk_HNux;`8xVN_thxJ~&FPEBzlx}D32*1_0QzD$T288xjMiQ%VD7(E zvyR5U4dw?(zNivHEda0z`c^W=-|n<4gNC46Por-j8LG?30pRMr7yA0>?gtn0HM{+K zekWJSL#q8ybW_lF=9`qIeaLriR@=G=RoRqiY@CU0XH?`_=bbBrT{ZF=t1g{WtYOGNQ} z{hSs&{Q@m~e!zze%8RDrnPp5yIozTmSFOw<&0;=g)r8}oE1b#dmXWqw1W}EqY_6zW zf1A)vqil!0 zocvbWeCia{rrfO{M&U-%UMwRIatLm|_X%3Hd>wZts+w=?$eaSw8m9~oo0|HBOx5Lf z!^@i#vSm$PN{j-Ss!w@Us-(2v(U!{D#MXC)0aTb4aflXxq8F69{`89{(6I+@#Y|u^ ztKCd?jt|(OnrC^;7Q4k>@Q#kl)HSQR^=6U-5$*-l49=5-VP&lrunK$N#N^j=h-p4frw?6P3 z(YSG7ErImTi>6e3lg!an2A`B{X5++;tOm5rvybb;)(PmWtxOSyOd8<&{&{mUIQEk;S>H&N=z&^a7X{X~2&l?!FfxLz@&{KE= zwPuLVk=Fq85w5Cps|t5-*0?&I|An{G#|}F{`6OJ|jA2Js2L_-0?k{)JvK44WR`typ z17A+8-y*VWl4GT5CH1r@v$n)0yj=u%iK%w&76o%`8*UlR5d z>z2&UX@u|U94`l?7di+Z_1eAYnje0cj(jz02A-jMKWD%(P^McM3YulqgG$2sF!MRu zG^hp6M87YfGCTSQITLv)y?(E$bj5d$qxZaaZ_fU!2~rrPHG_%rBSQMaX}I{sRdn&+ z9#qob$Mkm#!c}%v3$bi1)SQ<0ipaYEi-6rv%HxS`43Z_))#gFhC*qgJno$2Ia*OY~ zpL~W^_pNPU;$eR35?xQ4Pi;z&K3iobfcD&w#B*m)rwhJvJe~K2!)Tv**WX!Q!3znM!v{`kRk$u~Yg zdv3F--gO0u+fTLcs7aj77UFnh=?xo*ca}V9bxc{rxfY`<>unguJfg<24uf4yy zYYm8PZ-BuxYip@G&ZAwo+=z~T{oZucL3`3>lP9PKoDizg@t0DZesNxZ`~ptHg-e&w zIamCLrTeyU5Bna-Q9G|>-L~` z^Xra>pQOLs{t(^z;4_>WyNnjTuu65)8DK&l>aVGu0KMH6g%Uh*d^b1#m`=OR-JTBK zYkS&%$8A&{0*(P??^l_BWFw}=T!H@#c}j!$;f4R8h0pMHC;Q1iYO6V3<`QLpXs#+K z>g^h0e~6#*9L`IKY7P>mm6lZ@XaXf?Qx+W8doxSYQ`F~G8aoQAob&(gxzjfEkt;5w zTNf;*m(H2Zp7udy)B9<=c8EOsQGq&+t~)HzT@VyI@Ale$EA@hmU%Y$`J+bIn>f%`Y zvej$psb`kbWbO;F#bz@Zh=---##3pF=~HODD|P)fCVYhcxUZk<43o{qPP9I8mmtNn6bY10(+QD&D8Bakspdcb)*e-mykGk0_L8T%O4$!X5SbGq;;1*;6 zDvi6OP4p_<{w^!NWg30h5ONx5iLS-iFqT+6fKCBTq)`_VZ0^BrXHKVWw%nM0d)?ji z$6FpyVZHiLjq)NOk#yDxs{24n74hW5MDV;uyKgx1;FD0Tz&*e9cRh&XJ3)tFM+fDQ z5@O3X=PsfT>fVl${@6}u{PJr0*Mlp#D-k^ZB8l9L!onG*VX00)Y|`wq&8&cNRWh3t zMYpLh(`+cZr$8zmKv@M9>q`zmq(1~C$pp9Y_#S%6c8vMYn(t}nUPsSyi){aO<23%#2$gqaCSjb^h~5uD$@w{n&H##dEIpVZVnZ=w!!9umwsnbqHByXnIY_UgKPa zD#LW$^6*pinIB$7gWc1Z*$h$PydJ|CT*t^n8`I_+O;U`>%tFYvn3HkN+q~v+ zlKP(HE6AGH;F{LgkE#BYn#I=@*wnAweS4ZQfphk_o6S!zxrKgm#f>VQH^BBQkaj%s zGA*EKvy!j_D6MgZjdvMg#k%^R$ImmwSbtTz=gFt(m@l4BOV)5RQVvI=pMVE&63UCT zTMvq%eDq&#c}#oH-;5^lybcDr25I(v96{%4C1&CW$Wug@1`%FlmaV%!tLZIP36z=* z8+@lNXV6|dZB9dL2f3xy1Ul`1ucd45TEJ>@x614Hr?{S|Wtjk}B=v$&ck}GLOtCchgat{wys0* z`30sGb$Jtc$}S=*TL`;ZHsIJ1r(*l6J{R%Qft&{bysBysLs$)U)6rl41O5H}g;ZrH z)*y%FJkLC&lf@&Q<~fcs57N5YB5@R6{d#Sk%2K>{;WKpDslTF!mUgkzuUk1Dl|PZZ z!cu1y_CLauoVmFBHdE-8%Kj(i01XfjMmPfVz{)3?x~(jYkD8A-1g2p%DVC;yOByG?Q-(3t=UO`Q8ohrfCUn#fuQ z7!A^%O|=Z0z7Fl$S4ln#W1!g#`@`Lky!d7NF`OVR7;5nLv!>F)d$TjJ9~ghg)a{`q zs~vjR7cZboZ@gb6%nWgo%n;kPBtdTcq{{JrB;*wJ&ks8QyhLH{4Ytl&r_KYUu_6z1t0SLx0B?b<*ma-^f86Q5j@ z=GTZap~}RY%-fO5WK3agAT_yaeM02vh}KS;@H_tCJ!$edc3#(f>$%!n;jmyYz30mp z(iy+|8#@L${g!d^kdcSN9n*B`q>xq|5Q&K+F#IGn$Il@^xF62GC7f+|{I@Tl_kZVF zT2b4W&6%poHSj@->Qs#DUt?l?<0`mL|4KUI)$?eFO{c4z3cpavZKx@xz_U?}UBkrE zE)&=U)~k$s9oMG0WG6F)w)-#i*eXjXXaTQad-dSGx8;)CepPetF(asO!A@V-6gu^s z8|dw)pGOZbUdqJP#T~C4C1p+K-Lx5GrkeZUs3F~Wkj)oPGy)X&1%JDZ4mjz@^z$np zV3mIp6@sgg1{CTQ&q(1|H@D|1s(aCd9>S>vly9puu1cq;yoAeROx+Z{DIE@%Og~sG)WPcq~SUh)a7$ra9}mB{+kemKSxwjd-~`%viV*_4hxcsF|ED|VudCr)bA z*~9krg6z9S-#|yJ+4mv}T!bq)O<3^&sax6hFjXXZ3CGfvYv^BhKT5y5?q0g$rpKvo z4cp)orZ5vjN$-%-UR05?Q_+a9KnCDQZ-c68B2-`bJbmuyz3H3pJ6Po%z!YgP&(P)$ zCqiXicN3y-hk!{kcTJgC)H4_}fKJm2!u8xmY64l$AKu2&QwcNZu32T({=YA{mOlH_ zn|w(+1n6xhq9BzO`yMzPHGIaUyyUH-9k!fGZ+*oM^w#}%pq;myP2;QT9|l8X0Mz=x8rpR;hpzhJ$+YQ2Bp8wEn50937oD#kmaeAPh^xvg z9fIBxzacXFakOdxrpzF#GsgCJ;d?8zKEQFY%3NVWU(0>%4>{#ry6&DARNQ>1UK`%m zUP)A+s8zxZI}VAHUwV2y+I_pJ^xD1V&?|P|j&|C57Hv6ga;7l*nt^rn>uuBA##D9g6nea}O$H(^a71jUUaV>VB`n*{0+6;^9x;YDHRC^ zGvGP`r|QwxGse@-b7s+|Q+sL0ZD!D1W(Zxb=O@N`!|9P}nC@d>l82sH$_%JVk1ctD z-#86G(#pN~|G?(f~$V;ZG+pfJE-h zFBHz8GtQY8|BXKS!)vKBWu_`_@mSmeF-Yv`#)4}7{FK_xM1Z<`Vh{}C5@QA6#~Fr> zJLEGOPwVa*sQiEsk$xg7OQ(yIlzV$s!%cY8L3F-fz#CD2AJxIP$PT;>-ck2+AUp;( ztupM<(7M(1%J~!N@-yDg9f^B_u1LCBWA1h^B?41TB2M76gI>aBM|ftO%N_$Ro6P});*bzkuA-<5k&8moQ;X-2CDjoRO8SZn<%rVtf8yF{Xs6M z+9dG$S93{y6NH{35Pe5WjtrSp19n12U7jo|`?S}5j3)8(pk=qpmvJge+J5uox6wg+ zY(zt=USQ`Rr_h1`!~_V>eIS_?xhtE&Z@LM7Qm+etsriuwXGo>csx(@5+6}T~RhYmy zx8_TFYJ?E=yhXiy$1B9UZxopcvIkw4oqpVvXrrDLbT;=!1o0y&s#>WSw_P`}@#B(e zY|IDS9D;VxQJRKg1PC%(s{&2c{=A74b_E;0tq*4)?A(ody6I(1_)Wvy5eps&-NOtvG^{O--be=>cno)aP%X5k?HZq!brI7@9 z2<#k1VG%M32N4DF*3b_={#JVPUh`D7WEVnyUOG~e?K;|mc-j^d*><&VN0>adIcO)S zGbS80Yjez`G}x@EpmD^|w8?xs9TFerz~&oGq2K)1akR%)<7sepp91Iv#-1DW&kL6e zH$3H@)5HaI9dV>LOoXNi@1N&++O^bgD}G-=ua%f~O@|_QU=;=rYbkEEu1plCwuHX& z-u>z5*X+R_{~?8=sK%bqYTYI!Hm(Mu6>a0%AwX?t*zMG+KU0XRBCEEtrh-PMSSE}I zczr{h*1GMC>GX$hzL)mgmeGRNtYizJTdD4@-={muVH0%zXrc2cQb*nTepU$PaU9hPI)`MWv?AL z5;CZCQBF)A8J)-w$w?b?I|_WpG=KsL+EfZ8c|UD7QyvBo7dO_&SXXJiE-fFd(dlPh zLO;IrPV&aj^!xiEW00EgSIUeRAApmz@%=8kzU^HzKrd*j?8UXJ_EacR4Z7l=TkBg% zyKFmw&ivHj^s4z=sx2mddi~*FCf@@OHk%p*~F2Rat5OFc& zK`wSdVUEZ7_CMpUx?ei?8d|c7orXQrf+nN2z@zVKwtrfpj(x#So|w7LKzy{BAa@^* z&jGG+sIBBS=X2ZMY$8xuS$SFPx3U&8c3hC` zHQzQx-uoSoKSihg_%iy_t&6ELVY({S9l-aFAmX}(XX=f7UN12-kfLC`wR{FCR;u+a zr`c0II`z1N=!0*58C98~^|Oi7<+_QObZK30+bQMO7pAaYWCE7ej=9WK;>deCsJC~O zS`5hTKDLd2cG=(Qd%yiBJ-LjDe*6?gRP~y#2{?}A|4UrJ%LKm_Zg4uBpd%QSCpFLY z=AAw9dV39|UPyhnwu;{N$~p9v6W&BSao@xm%8ET?Hdh)gzimM>%7ETBsjV*xQoN%X zz-X)nEGoS-YBMBllp?AQAOMCy*5C8gv-G{+T}_u>{{SstJCQ0}O^8rpjnioB zjS3)_1me*jUm8PjQYHTIYd8Tz(9wh>38ALm15X4Udl-u?=D$E$ZI z6f+NT)T0vA^fv)Z>{D<}3Sdl`eVmz?R8^Rj0cEK$Egi^qv@n3U$;e~-*@Tj!96Na< z1xcGxL4&CZxj}^|080DUJqzfkm;Qq;zvWR{zNVK;cPFdw>gd+vc@eq%adBub7RNx> z3>+sMUGPFJ%j>GkaNP|;Yirl>DgCtDwj0q0-||v==YhM?RBjfcAj6f^k2YIOw6LUN zm%I^xJX%FmXI`7gNxP7R5>0?kas1ijH0N*Xk*sbeA+QvI5HsRF4LC1XHQH1t0Ttg{ z{;vlfqhDV6A4cuDpO(D9Dm8s;}xl@bqH3?C2-C2#TF0wIvH>sXHY}Bdj@wK(9zx;40LSg? zj8EWjHsj1m^qSqbpd((p3+=b-TpAaQ4S0r%U%A49T*Z;}=}CjL$|E67@gmDo;cYTV zlM0N=ZeQMZC!norZ(|dtF%{E#aZ`zscTr520!czB@2Li*va1t^JHiht|PY(nHF0DgeOlQb~ke>S*=dMEYL z+}Ts;CG%&{>-L&U`|r9PZN&&h6u=Hp&4&()nhGb{(`bd=m}GX@O42MEK~}i1EIy08 zn3o;H0MgWaQwCs~AXM}uWl`mdW(IN4&~^3^!13l*x_lbt*CUIbqX(XRmTrIWNxJ*t zr)lwv1N6j_l`6+z&AJ*5tV1q-wNYN&Oc?a@23$rSn%v7}%B*rso!CuV&zwkGa;Kx0 z?>U#Y-+U_VHfM9%xVNXlHsIVtg(9HDD@m@$5VU!gq_4c{b{Y8rQR&JiW^Gmm5Ere; zVk(r%08GFb@}%o#O%cs<{jA(ODa@2t8&E#NK($v*d69XClv;Oq9sP>~{q*Q#i`X7_ zXa#pHzU#p!Xhq*TRbE^VdJs6O%$S>iFbK!4+s>wWTWq4*na`R&jpl5+ap=x9W!w9v ziPOmOcN>uiFGJd}y;+wP9V&~;U6f=li`=qmp=~t*k{E!EYF-q>NZOEjH$xJXbEb4w zS?~t@cU?K!YO35<5C9y7q^q3oRLT4tNL5xD0YQ}_RD4%*{DX|dpw_3U=I6+|w4JLg z#XxDW*?FrEi!d!)5o+w%Oh6k|&(;PrQI(3$s>_=&Wc*u!3LEx}fu=yx6vP>6oNJK* zeXP!~=9^wIZGSTg1=D?D`&~OKOiemD8pON<+&mg;6GJnsrX|F!wBTkw;_9nmuSUhL$scqE+pz8_l+aGi%qF zFw-+>WLf4MXDRPZg1AaTX(}4Ot_Z}Ptg-hzjNBB{P@X3*gqh8pRH=nJ7iWZkK8M$3c*_0^2Zc z&06jROsllZ;^Ay%{kF}$vvBwPJ^O?{6e z-l%zx)2iAPa7$X}w)g;SRe2i;V=9$x%S>(Y-qO6uwh;vzZ6e9Oj*6a`c)sJto0Mk} zCH-`pI!+4=pA*Nh@`Ab8*!e{#iIwdfH>Ia4wFw?6E8c!yZ*STrGR~pS=pajLEtLh+ zAWKxjR%;1wo}@XmO#v?o)TONG2xZ70$y4}KbeVh7_1#vhB1w*$EIIONDr}qsE6Mql zq@kF0e905X&C{sTwk{&e#y8PklIAc;8;No}wzJ8G>HTdhQK>_kmbje`)0KxfiU_^r zL2SwDr7qJ)k`xv-A?Pe-LRM3!EGZcl4oMM@D2*|SF~ylwMLlm>N43?p{l)d0GeeKf zi|KEJO`p`o#x#AosJSu$SFtIC(Z5ggYb2R&No!nMx*olTv#$u&)wk}XkZ1sN^U&Nm^1Bn>2wZEJ(PTo?*Yj3c<{xy_>W zwz{cEwd~lr3BYEN;F?L&u#DV+D5scdyuGaKzr4VgS@Ii-2*2imzsfU*Sslzp(MpoA zD*`~IbOvBE`C073GP(?>h5zDI->lbb<#VSwJ8j#Z+t~f>>iyWsF!o8Q=w*VFbOati z9_d~N&YlUOkLI2TX;Q<~+-Mh4t?k(Ih8x?-yiD;aiXhQA?YG*AZNS3WZE5qT>BTbw*v2hK#=cGxK=$F6zC2g_A7Ntz; znl)?ax#ym15ScRV*;ZV7DVvigAdaG z{LlY1q+<90v4HrC-Ut{M7WJS1{3ji9$RTPh@=W*~jN!QBj#K{z0|Ijr1|V9^%U}L- z`p$R0qliMTmo~=b%a_N_7Y=AbNeL!ptvAMf#~pX5^@uzA`}cj?hbAEo>5 zyRRW+(i*>-`ZshM8VH)HK;U$DcPpVackWyz0Bjji7W?1y{Aw8vlc))?**H3fYGac2Z%8PU1;9Bb=jm!5?jOZ~uDk9!ddEB7 zL7)26r|1Vi_<5N3i zH+AY%^=~liEZUYW(a3KOJ@imDhu8yQ2A5RTXOirNYeMTe>#VbA#flXTKNMb%Jo1Qo zU3%%Ibn2<6(l@^GjYv%(39BI~FTdYD0>273sh2 zw%aNd{OYT(R(=L-hA`2H-7V4#Quwv6eXZi$NgFS%XhzQOnqT-LZo26vH9r_P z#{A4P&mGiFp#OPmW!7*+tD6{6MmH;Yg23C=Fx7weTwQz*vtw^2kVS z5@Y}7H@~UA-+lMpN9?O4XJHm7`j+_sObRTr^mR{*DqzDvbw2y-vxgB<_`a`s&1;l| zwK=f)-~RTu)3?9kt8reI9~DF z%P+q?Qj?Tt$NIxBR0JeimPVf8DyTspQ2wA^YF8u{Y+^H#~ynu9dX1Fw9!Tz4GYD>-vMnVYQ1s2^UptDNkGi8 zFgmOqG_>!3|NHd70}rV66w_z*>eckuzy4LdzV)qdDdr^RnXQ8|iHRqzO_<s45fYLO>>n9oup7G?9Po`JA;uQ^3t1P_Dj)4V_Qfc&Xmu%{^$&l~Gw!-g0 zxWD+tFNR50SxDUD=%bHTn=S2_H*X#-S+Yd=3UntG2mm%W!d8$z;&Uk5Hv$z})PEr= zj0s^qgcMCStC$`7d8Xr#=f~f$39xeIN=2xcm)&;TE%Nsuh$QB;=bn42h)4L?d+)vJ z)vtcF`U^W+HxI?10w*3+=&iTjx{!#D5D)w<-}}yYY3$I^~;r z&wJjZ_M6Nx_Tc9}_qj$GSf4Zf-=F;CCsnk^7T++beAomozvT=dNm7dB{^A$ENEcmn z5se=|UM(WRT5&csHr;;v?WfLz`t-4neM}J~HUkzc%J!c#XHG-a1Tn=C490_n+O`&#Hnj(B6abbfB*a6 ziUAarE062I?=zhpgeRSRQtEFo{`bHC{c4@CkFU7mieZ9B_bp+((3;?<0^=}oEZf4? zCH1ms3j;8DILQW+ze(PCnM|mrm_XR%NDP9#6`pi?_#glH$13)U4ViRp6U3W`AAVSc zdBZ9v4nzs?%wnR%d9HCj{1*+a3EhIZ4fxu%{Yt`K_Q%Vu?M&U2 z!2HXGk0SOo3<0^9u96E;fMC-6Nnmb8?^mqLhfn%2&DwO9GGRpX3aj#%cL;-PuDM2q zZ=pHJd9qHjC-E#0T!;)H3>f9Wv~414>fbTe09u`6EjBjd@)DcJ0dF!KJyBpD$OAwu zU6Lstd+agVYp=aVNNiciH^2GK%EpBK4%^>EZBJ4q^QxKp9E*Bv!X)H(qDCT95+Ovq z$L51?Lr;5_c>q&A=bUrY9Q3oaa^0qsoiu4;ZU!FCV<@Umo@xF?8R^`T#DkD680|aX z`OXHxhC`Wn{tGX>aD>#CapB((I$!$ImsHqKt{vsXEh?ODTh&_al*%Xxg{0+Z>&YYU zd*Az%3XDnClS$<7_19mo!ZEPP;50%4PFg50Y18Vt30b5)ObAFVi~;GuahiZ^d@!!4 zXNP3ka?35%`<-^$Db@jm_XyJ=)RrZ5YYO0Di|uc+L2bGa6%Fu${RF8k8o*IU9W~7K z2|HwUsju7muYdjPm8}n{FFqxk{F5xrLTqs#s-vNtRpMza_DH*2GVF9aIB?z!ix7i@i)4_Gi=4U@!@XPHFMP%aMd+$|iEeT~{97TruitId89W9_1vM7*v@YIX9 zK77D7zTkojlop_;#NvPl7FxhdU;5HvA;_|NuTw-=OwwLa91?dRBJz)a{6k4^Q7N$* zA(8gocVCs6sE40g1&9%rxT1+frSc|8J6RX`eUw^^xhlG9FOPZJ1?AN~bwMNWX;`SgU#Q0IMC+b?! zj-)gnf~iXV=Rg0MBuXOQejFeD=tpC(Rb)D_$#DalU|!u*;n^zYk#FT`sxi{Cljh`d zHizbIKactl!VM&-=QJ5Tw6-EPe^!l;w2DeFE9@yq_^9#7dVoC!as`pWN-?TD;L zB!q<%eZ)Kk(c^nCfHV!wMhmckXzN%8P5na8!trpVJRxs*$SiA$1Yu0W(Zexzm6@aDM>Az@!L=Ez_Xe zqACy(dXCe;#QBWpLPn|1eA4h2FjhP>605%Og)bO0fxP>*+DTxbR8W2a77KA)sQM_m zM8+5rc=QAL+DIZoco37WXAl;}3Kk=!ZRs!?rTsgK&}( zjndXdu7^B{r=EH$vev}3N1}`w&xurF_+b!r3Y!6XC=kh6@EHh}Lz@4~W^IaS4%@LN zK#>PjDtKbyHJ6%xBn64R2I&A05#*h>;(${O8v+{%Dq@<`3gdg*+uo)am+teBFd04r zBa2G~#`7aA3aKS|2iS~YEYN%qN=@>Cq*V**(yJ1ZtZ_b?D0E>xMlPT0wFP@(PKVF>QB{1YPSU-!D#(HUo)K~kp>W&k$M90_YCA@SfGJry_&cFMN+ zDQR=(UFWO_QLQjx0erH_CYvZNGH#8;)H5mi$`XYs>gb46;G)B9aO#2hVFKvba##@H z*n#+^fFn%&MfW0%3KQvY-6lQf<6b6GRF4{jf19;rS$?nA^XtgK^4A7GQGbbFBJKBj zO+>300LF%La1>c3L2<#?6fWm33s@(QkRDYGAnBpRuKxMYf39%qL^|rQ;NaT-`@jD? z43{oXpFPT$P(mTt^G~``67UzIJoEC* zQVL#ntGB3s3tU>C>4KoUj`&)TyqvV1o0S>QfD>@*&>}AQ4>KVmf|7N5D>N% zWZzacvx&lGj8fCF&jcf}C{s91`e(@u>^KHsTa2T(*Fqr>B!>>pAn$>UgB)#f5*AH- zWe{y%j&B2*9d#`+Q*lNBx4U{=|h5?C*wG?PInH~dUU$%XczL!=|815!B( zW539rf&x-1cn)!Kc$}ByLn+FhPeKjqNCuGif%Rk=7oBkebrY$xXt|}Q$%^U^n+0C0FRqDV7I{!^Oo`kEs3V-& zRs{2q%r(>>;FWfO5#SX*5b20x03ZxN!AXKv7EMsr5}{tffylzN#jMbM7*Xed>46yn z)CFcsR3@M>1caHh;QZSNhNxT#+dbg6GSm{~g%%JAk=kkLpRlX=3Uoh%WgNp25K+$r1O(SYCf$4A``$)but>7F$&?)LaBFeX4JGPwT*D0;?7_UDu(a>en2;b3 z%lhaq-SZ#*9Dkv@C#3^Z(AZSwEa^V-k~Nf6W6TrsBv5=KFzL2}1?w$N%W(FhzE|)$ zh#$p363UHA^~P^dxq0A$2U@RXk%-cTF@Q21ETSG^@&!}^>Uy}hz3wqb2=5?ntO%zQ zaN`#)e2RYct6vR+0>gQDk75kLU&pneE((X&ol1I)96*q9&yBIgg`R}-11mr5B$DrR zTUi)QQdOX&{Kmb~8iS;v?wE8X0wWdFU;P!XQP4jZ1e`ReFiD3_3Rmg~MmkJ8))M%N z&^(aJjif2P)30fW6XpX<5wz+8;4DhW!^up7PXI;(!p69xDjbE_0hf-w7amWN1TVrN zn1xo`<^+^DF>w9hO_tPMbc2a=9>Hsjg@W_wR_u&3&O8|-ptb;RDvd>KYDi(6l%x3oN+f8R!gUY!V?NsObOqfBzSG4$y4T90K>=fB*ee7a9B&E#VpF#C?72 z{p}ssAXvQ-r{2pC*?~w9LXH3y7ck*)8i9F08lw?r7=>4dFUNIY)4u9euWBrAxUdCw zAvQBMj2;ii=EuG87xw_;!TYcj5!kOE{_uy&`G&EI6bU;J1@{0dFgBfxM~6j+6aw}E zD&lZI{W|zP2o&mS(d#R7j*d-m+Mzr)>x*+?5`?rm)Ot*U=~2@h?2PBUVQRzNSp6{?|YG6 zX1P9ElfxMm?oS9NgMuW~zoX6{%_O4IX|b7bF4{}O-UO3^_EOdX#sLPA1s~Cr3BV)* zLKA8himtbSNrie2XmokngXz*5b^Pdgh~hESlcXJB!moVgD^&;@%+9pwhmBxOBkm+; zVoMo-EeAF^Ka9h!+gfRkEPX@h<^kTLjT2(nXzK(uH7;od(F3Teg2fk>`g%_}_$83U zLP6;$)Ju5L(Fjs6IR-c>HW@-mI#EF50aO{vbdkdkDF;VcQUl4bMA8dJ^!Iw7dI6sR zI4VMFMfBE7BP7N2oHI#P*iQS7s)i&O5x{u3DCTlwb$ciGI=XWAC#aU(A z20F_)L@MZvBdKww?q4XgL|L?oG9-VJmQqxeU{cJ!D8?0+715dQWge6 zM>2q{gkN-{1(UjVeL=&UJPm&?4AWJXq#QdsZ-3kL@GFxfC;eVeBGSv5ld=fY_E=Wrs$7|Xt+2Cf?jb}Lm2#=)VaKJPMzy61 zPn5oi(l$*}Dyp768260faoWIAQN|S9BJ%#zvKfm^|JA!FMQ!SEYPWulq&cU>*^8R< zq>NF0-jn9948K{TuqfID$Write0Y?r>fjY9WyLG1umpt~H!%_r%0iJpk7_K`aO>H4 zI3JaCGUI@~wdQpx(9+VoRiVWApSO8az z>*`DiH3um05s*^U3IN11THsK$o23%a!(lx?kBwx^aJN$ z4WwWwTs(yGX&psb7$JU#s!EhCqd*4DSW&)>N=7`RApPh}Syxx(S{N&yS=xll_j2Df zbsoP1um>s*aXxA|qz*%0C#;YB8~4NY!;L#IP84>D6vzDI938zF&xGF!GsD(^rL zs4hc^F5Uy}^@I~nXw;j_^-(>DYCpXc`LBQdt7<b{)S5BNR{*!5%;{6YAQ5DS&YaG!+h1 zKk9e}GXFr>MY%JWoNmg<`hmbvt1ocs7(Ys*&ph)?TD*91qmmK?fi(me=s^b^q~;41 zk02miR~HU~<;~bjT6A zn+Eqj=Lkv>+3xGHFDL`(a~&sH8oa5Ql6S zc@IVs6$b|X9lpaR!$!uqQLYNcfO*9;-~i$-<^mE;q*fH@D2N*em;=(?^bSCn1I!5+9hj5eUlD7G{fB+3!>HrB*nffnfpY;W3Z^2&FB%}2CFU#1p=Z)9 zYE-ob!vuj7qKPE{6-0#>l?jW1%>?8iY%Uy-6gVG9LSOyrSCxbpCLx;zLIy59ke3AllfvJ?P9pcx7J`~gl3a8MM~j7#0W zY(P8%Fzsa_zx1UqDF&z$y$~aQFLmZ&bQ~OLJRt|*#V~eUA1{&mm^&eQm_K4Bg`1FK zo&;1A%{BnBjtvbYArOr2e*rE6G#?zmIKVSre);7}q6soH)MiLQFg-jYn6}jJXc5{w;Vgmwt&DylmUFd$h^j05Y0abWCFr{(!R@PQAgu}F(_NMbNG zc_tt)33HQYlzGP-gE3Kn3jr-s#wr>a*6xA}E>MgJ2lgV6fv`8BeL_pZb@Vk9P*IEt znj+>CKrwi>y#5(mMo?{65{9U9uo(qL3HA_Rx1a{fWaGdjL1GBOLUotp&_fSZHT#Dj zez;l;OgxAYgn$E^5}N|d1D}JyL6kUvIE3rT0f|N$T8@Bt!kz?n9S%rYEU+vfF!A5? zrZ+WyhXy1VJG$lIfC>+yN1eak4hsYhX(!B2<}(TbDGo4H)&kEC{JJ;4`OU+mkM2jo z-|)4g))7Q25ZlKbb4)`zf%#0`m#?*J+yV6Px9>>C|k9K?jV0MnoVq!Z^zb68IWDB))gk4E^)Chh4JBf5Dv2U^gSwl1fVeq$`cTuzWn7ct8oF^49}Ri zn{X67c#=lpwmS(&@YeO_nb;JvaRHhE!j%bwT_@+_g$;uV#v+Is2co0^>;vt;MOs6x z!={66EGi@xRhnGt+*dX_4q(=aDu_iA)$Opu4pYRBF-R*c5GMQrd+xc1ItQEhl1na) zY;1xJgJ*-35GR`uOcDxEc&zds*8ua>d+*701z|Z&YUrM6agqtM5s8Zf3=YH(l~s_2 z@%*rhp+!Iv;#ovBm%n%}X_^UDTFf=vSKiAyLh8x^8$W7oz`#NLSZj;{e=%l2g~?pP zOoJ8>4$+BE0YnJxQSL8u7M>R!#Yhea7x+PBKY;l0te6X#69^BOWn%Wq{h`@HOAzJ( z#w)mkMQ8v<9iTg*4VG-WFp8>3Wg$=yqIj6GNCH7E(nl5$wmuw8;zxjbCK4Q+7&b8^ z9qdbe!QrHn#Skq5NU{0`7O5il)HjEyk$8X1F-JGfyZ!b%D2(Qd=M!}nYAL`j!zjU~ zISepW;J1Kni5K9qle*R<=&}K&y$c&>M|LnN%0kngQxErs-4Crmq6jc25YmG)O4LRnT1Z-GByo}% zw!APWVPN{RK^=$BLqdP>bn9JbO_?z60V#-1G*EqE4!}&YW!5tHkd)ABKs>k>Y;|F( z0*eiG9djBcCW($jsj(&*c`^n_d)!|%KbVQ)ThWCCRP|}oHj3SM(n3sU;5r83?z``< zUKBJW1tSh?L2(URR=61?h+0Q8)<%@r z5l9aF9=4}+xP@;5wyO9F(uh}A^CZ7Po)bO`Nm4S|Ao}JE$F~t43_pWN{310$J);oX z5Ht{RP{Q}nWH@ePEHFQGFxRj*JkErY&tW1U+$`W1;|Qhn@@socZv=iYFH8n@B$CXrrG4i4BP_aec8b zbs~|?ga#qlfFK5JbeJI$vXkp#;Y9r#PGny2eY1N{Z*R|tMS>;)2bMS_aln&}E=NgS zb;8%dnBgVIhJwigr(&Ev7?-Z9OEgGCXaVr4AOax00U@!(rUv5^bvdf%pM*8zQfeb5 zQ7GF_7&n+3Gyt6_Di!~{=bl?a_i>B3xKwMA&T-5D;Yko7d^YkMXo~0&kwl1k6B^MB zB?KA=G^%=92J;Ldggy~4Wgz*XonQ~c#{z9rG*TMv)qq8G&xO<$CV=cJsA}TP#UzPJ zj_<@#1PLfezTs5cI1NMhD`4Zmi~NgU{G##OfBy5I>Mvs8a$J1z#f`)uv3-S!{OM1B zZro%3{CSc0MNAnRP@)RhM8arnd;nl(h(W_GKB;3{?SJnU`McYW8vD3I!b9PZ6>MWs zwXngEl8L{NM6eIxyc3B8$qj-Fhtc91IqC;C4IE&I8N>F68Z7w-5(b1SEXgrgXe0q4 zefRd;Z&w-!G6bO|y!hhshKC={A<+zU+fg#eh1B)KzpdLb49Vi}?^qim(~94oGNh za4-olXGChifWp_+9cxjM4ro8(EXCO2qk(fwIs_xZ3o5%XW`uJk3-8Dyk5pO$LdKAw z$QzMHE;tb8i-JNI69MDF8i~U*N^^pGFU?GKMu{XctUrE(IfgWc7KgnF$0;&Y!F(X; zB`Xs12wx3q05E^D4miY-XkrEy5gF)+a$s2yAvOu;h>D3sAH;rz+hWr2LS~zUyl~(= ziB-#D%LG7@iH(MhhQ$nfZ9!C6IOG+;{+A60<|EE9j0c+wn^fY_$R~#?ir?ttL6{Nh zy)IRB4ljg&jRR*FHZB+jo*nA0gdn3fq@+XRTo8+{Jt6l2nRgh!FcdH}k>I++kb>eB z5I&wCITB!~`T-3E1PrqZ5*GnLH|OelIyt$IuirqK!OlV2l$~7cE+4xUG=eGF0l9TjPTpwe9XHL?gk_a057{ZbTCn(dP{5>xzmu$kZy7av0;H=$3ZGVJ(p{Sdk{(( z3yemRfppaq++5O4h4^7#BOC>%jb6eDLV%47ViTq!@gmNemmCRyrYz zG=Ucwp+(uCl0BwxHar*Zg@P!36YB@E@xY8AnXxgWR7?0tph;0s?1O7cautP|qhjub zQGv0*bO4it#)7edxk$kc!lq*K$QpoIN%}Aj$tTbmfF7sExMePpVhk+=2i8HPG?)?Q z07+hO>I&iGg$<5<0tSV!u1-+TKKooFFJsZ7r+I!B4;u?I0%d)`fTf3|?ofo}mc4}Y zV47fTVBnH&jo%9KV-F#<8H^F3Y4$u->jlIzlM4&iqbqW1olfj1n!WX`v zzK2SO{BeEZ^olr09Y_?hy`d693MI9=0;k1>z=Y#BNMr%y2uoLNPHaXYc5HYoYPe!d zHaIq_6upSo8eCke+Vt{PTmwu>HaE`0xb=B}7g!hro(n2>SR&xxASmg&t7949g`^pb z8H5MtXt+;3m)$c=*zkA{ zMExNjhy9gG7E55I@~4L^_7?~nxf<{dK(b3>62^zmvDV@4PiR+;v0!{s(u}=^b7fATRbbCxeehdEr^FAC zwN+b|{srosR`dmkYsx5_32Ge@bFe5_bVy04#Nmyf)O^vVD{CA$M%<)DHJ>zX@nzd8 zswglB6E!mHnkNCFG*nB2dz%2EL_LQv4!zlF-Ze;S&=EJ6;b#ygp4Rd$2?;7s7GRc+ z#BE^!`h>tE3Y!}0Bh+jZ2EmDz#0mBCSy59>xSgIH6P4E;)e|o5+TrulAjI^sNA>1Q z`#x?C!fLxbXH*hW_}WS5CADD>kHPfZsOQq>F?^jc#;vbD)~LBm zTI29s#zEHUV=!rTUz%p#>dru6rhvFB>~j!1vf`itl+~`9#^AEYjxfGJrT6H;gJfwvb~-C$GW!)T44(6rk+M%>o!Y){$rI=rm&P z6hV-u=RyNOwv}Cb#t3=r7!9qvM#DZf?R}BTJjS{Mj~!#j=rZq$Y#BDz9eC^*J4S(a zn$*-L4+C@8m*$jXip<#2DOIhbg@LuL7NAW0fU%9S;Sg5YU5N8GNG)OvNx&*uq4$O* zDUEGLFR>kSmZqLXwb&j_jK&1KB%E?P8a22$`@6{TC+){k9R@7QDHDfxp4Qnm?OfAE zjl*0|0=~2bZ;WaqZYr6g?rqxuGpQ~i?%9$$BHMHVjv9kat8Lq&7~8xRQ=-oj^0w*v zo8%L)W&oWMb2sVWo5xg0B32UyU`x<>q(jpV|cqT2!}^q(w<0#w4d$)OR*qKh7bQM9eZa(@2D^Q!hQa<-&tt@MM(u)SR@-KU(#l=nqem-K(#{pw>1h77pqQV4n=yEvwD|{aQ^Cn2x-l4l$y z<8wPDhu_3w-^z@+B*qfwa7xN<3%|D=b2~BvbCsDE?AI`P{wU_tE+=Wbn(EqS7rL#; zbxvEnI3jCTI&74OI?nT(Hohbeby5VP6YO$RO)MOpXm_oCoQFQEbFt`*zAX0)N1aSG z-zcYWcyGfASZ|dkV56eNW$)$D7?Lsqlhmp>hL9%t;>KG%ic^L2sFvY-=Z%%8#i`RO3R?}kP8%>FM`8K$1rvT*{t{Eu z)IpDQpVn7H^_P~XUZ*TKD!%~MQ^CStDvZ^vIkEfXS?{X(hnD8|Jirs%?>r zY9po6vAWOl8+1$v zD32M?F161j71$QnAS&U*)@DzOZsf_82s`vlAlHg4=Gl_OVqzj?@t>* zCQXvyc)C*2B0K8(d6|mJ4-Zb4#y;Z2I{Z-GMz(zU)z5B?UyR)&$RCZUpn|laKfLvy1EX6 zra&b%4^3(;QAV?cyp@Ax;2x$`6t)9StQFAI?Wl_wJA7JZ0e@9@clW_M%{v;0fHqKH zyLRmyW&k%-DwWy%aV;mz-elatE z{l|?Pw?Jr}bQ}PD0JQ)otXsG4qru0Oc0>hiVZ3e8gQBvvOu}4c+n;f9`6AGgY+~t2 z-ua~H%xDPN(uUQMxGn;mY5F6e?LhUfQ(WAiPe4j=olSw0SQGe>9zIx~oPz7C^fJLz zOv9A3)jn(hR!w>kjva*&zvTIxRIOIe655U`3K;eo90srtI5KcrPfyR+bXB6r>Z(=} z{b<=_ozkOm>=^xOKi;s~e>&U!U*~=01bfO$V*tuQ5SRrX`vgwv?d=s!V2CeNEgJB# zY^T>4F^wG?5UNtcdC$auG86xgb$X5~3ru7JYu2nej4yO1s}Zy7z6T)}qHtUfxAl?8 z%W&q69b-o{$tum=m6GY^=hM6{LYshwTTla zo)@NB6J!t*{h|>&eE8vqXKcCUmd7v?IFYr2xiK8c(OyQ-&$hAnyuIc}8{+L~@u1tn zxVL(qM_J^-(_SMqZYKOk*vo%5(tDBN7pB$17=ZW%_;7`@Jp1gk6Q@m^b|Ak!oL~F$ zYYS!yU1jt|w~=yb{oKoD0=DhV@?`DB%}1QC!DilVyDn3P9(*>X!>0Ixt?pyc%7Y1h zke}fx)bsP};zu8S^cocQh|~}A`#oE1Fv`OSf}3x~gWHWYf|v0>=ks^a3}*3bBEKet zIak(x0co4r)~OhE?Ia(CX-Ae;ZnSo!4j0h~!vq6h0}c^Gg0CSTwtoo#Jo)t?)85VO k;JXW89=eHcOZam9KTAWGz?e$jBme*a07*qoM6N<$f}jE#od5s; literal 0 HcmV?d00001 diff --git a/res/icon.ico b/res/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..74ee25d1c3925bfd8d490a5794b67d7e7beec445 GIT binary patch literal 42344 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 b_;UO|OGB5ym`dIx00000NkvXXu0mjf-*x2X literal 0 HcmV?d00001 diff --git a/src/app.rs b/src/app.rs index 801bd56..b13b484 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,6 @@ use crate::breakout::{BreakoutGame, control_from_matrix}; -use crate::connection::ConnectionManager; -use crate::force::{ForceEstimatorState, HudSpatialForce}; +use crate::connection::{ConnectionManager, ConnectionState}; +use crate::force::{ForceEstimatorState, HAND_FINGERTIP_COUNT, HudSpatialForce}; use crate::recording::Recorder; use crate::render::{ActiveMode, FingerMode, HandGatewayMode}; use crate::style::{ONE_DARK_PRO, apply_fonts, apply_theme, layout}; @@ -25,7 +25,16 @@ use std::{ const SUMMARY_POINTS_PER_SERIES: usize = 42; const HAND_FORCE_PANEL_COUNT: usize = 7; const HAND_FORCE_SEGMENT_COUNTS: [usize; HAND_FORCE_PANEL_COUNT] = [84, 84, 84, 84, 84, 70, 44]; +const HAND_IMAGE_SIZE_PX: [f32; 2] = [971.0, 1117.0]; +const HAND_FINGERTIP_FORCE_ANCHORS_PX: [[f32; 2]; HAND_FINGERTIP_COUNT] = [ + [265.0, 495.0], + [359.0, 260.0], + [482.0, 228.0], + [596.0, 265.0], + [693.0, 370.0], +]; const RATE_UPDATE_INTERVAL: Duration = Duration::from_millis(500); +const DATA_IDLE_PANEL_EXIT_DELAY: Duration = Duration::from_secs(3); pub struct EskinDesktopApp { connect_panel: FloatingPanelState, @@ -47,10 +56,12 @@ pub struct EskinDesktopApp { hand_signal_histories: [Vec; HAND_FORCE_PANEL_COUNT], force_estimator: ForceEstimatorState, latest_spatial_force: Option, + latest_hand_spatial_forces: [Option; HAND_FINGERTIP_COUNT], spatial_force_panel_force: Option, latest_raw_matrix: Vec, latest_matrix_rows: u32, latest_matrix_cols: u32, + last_sample_at: Option, breakout_visible: bool, breakout_game: BreakoutGame, live_rates: LiveRateMeters, @@ -158,10 +169,12 @@ impl EskinDesktopApp { hand_signal_histories: std::array::from_fn(|_| Vec::with_capacity(128)), force_estimator: ForceEstimatorState::new(), latest_spatial_force: None, + latest_hand_spatial_forces: [None; HAND_FINGERTIP_COUNT], spatial_force_panel_force: None, latest_raw_matrix: Vec::new(), latest_matrix_rows: MATRIX_ROWS, latest_matrix_cols: MATRIX_COLS, + last_sample_at: None, breakout_visible: false, breakout_game: BreakoutGame::default(), live_rates: LiveRateMeters::new(), @@ -185,35 +198,85 @@ impl EskinDesktopApp { active_mode: self.active_mode.clone(), }, )); + self.paint_spatial_force_overlay(ui, rect); } fn paint_spatial_force_overlay(&self, ui: &egui::Ui, rect: egui::Rect) { + if !self.recent_sample_is_fresh() { + return; + } + + if matches!(self.active_mode, ActiveMode::Hand(_)) { + self.paint_hand_spatial_force_overlay(ui, rect); + return; + } + let Some(force) = self.latest_spatial_force else { return; }; - let painter = ui.painter(); let center = rect.center(); - let magnitude = force.magnitude.clamp(0.0, 1.8); - let length = 34.0 + magnitude * 54.0; - let angle = force.angle_deg.to_radians(); - let direction = egui::vec2(angle.cos(), angle.sin()); - let end = center + direction * length; - let side = egui::vec2(-direction.y, direction.x); - let color = egui::Color32::from_rgb(255, 196, 54); - let glow = egui::Color32::from_rgba_unmultiplied(255, 196, 54, 58); + paint_spatial_force_arrow(ui.painter(), center, force, 34.0, 54.0); + } - painter.line_segment([center, end], egui::Stroke::new(9.0, glow)); - painter.line_segment([center, end], egui::Stroke::new(2.4, color)); - painter.line_segment( - [end, end - direction * 14.0 + side * 7.0], - egui::Stroke::new(2.4, color), - ); - painter.line_segment( - [end, end - direction * 14.0 - side * 7.0], - egui::Stroke::new(2.4, color), - ); - painter.circle_filled(center, 4.2, color); + fn paint_hand_spatial_force_overlay(&self, ui: &egui::Ui, rect: egui::Rect) { + for (force, anchor_px) in self + .latest_hand_spatial_forces + .iter() + .zip(HAND_FINGERTIP_FORCE_ANCHORS_PX) + { + let Some(force) = force else { + continue; + }; + + let center = hand_image_pixel_to_screen(rect, anchor_px); + paint_spatial_force_arrow(ui.painter(), center, *force, 20.0, 34.0); + } + } + + fn strongest_hand_spatial_force(&self) -> Option { + self.latest_hand_spatial_forces + .iter() + .flatten() + .copied() + .max_by(|a, b| a.magnitude.total_cmp(&b.magnitude)) + } + + fn analyze_spatial_force(&mut self, values: &[u32]) { + if matches!(self.config_state.mode, SerialMode::Hand) { + self.latest_hand_spatial_forces = self.force_estimator.analyze_fingertips(values); + self.latest_spatial_force = self.strongest_hand_spatial_force(); + } else { + self.latest_spatial_force = self.force_estimator.analyze(values); + self.latest_hand_spatial_forces = [None; HAND_FINGERTIP_COUNT]; + } + } + + fn connection_has_live_data(&self) -> bool { + matches!( + self.connection.state(), + ConnectionState::Connected | ConnectionState::Streaming + ) + } + + fn recent_sample_is_fresh(&self) -> bool { + self.last_sample_at + .is_some_and(|sample_at| sample_at.elapsed() <= DATA_IDLE_PANEL_EXIT_DELAY) + } + + fn hand_force_panels_visible(&self) -> bool { + self.connection_has_live_data() && self.recent_sample_is_fresh() + } + + fn sync_disconnected_live_state(&mut self) { + if self.connection_has_live_data() { + return; + } + + self.last_sample_at = None; + self.latest_spatial_force = None; + self.latest_hand_spatial_forces = [None; HAND_FINGERTIP_COUNT]; + self.spatial_force_panel_force = None; } fn draw_workspace(&mut self, ui: &mut egui::Ui) { @@ -264,6 +327,7 @@ impl EskinDesktopApp { fn update_pressure_matrix(&mut self) { if let Some(sample) = self.connection.take_latest_sample() { + self.last_sample_at = Some(Instant::now()); normalize_pressure_sample( &sample.matrix, sample.rows, @@ -276,7 +340,7 @@ impl EskinDesktopApp { self.latest_matrix_rows = sample.rows; self.latest_matrix_cols = sample.cols; - self.latest_spatial_force = self.force_estimator.analyze(&sample.matrix); + self.analyze_spatial_force(&sample.matrix); // Keep JE-Skin's summary path separate from the optional spatial-force vector. let raw_total = sample @@ -431,7 +495,11 @@ impl EskinDesktopApp { } self.stats_panel.visible = false; - draw_hand_force_panels(ctx, true, &self.hand_signal_histories); + draw_hand_force_panels( + ctx, + self.hand_force_panels_visible(), + &self.hand_signal_histories, + ); } fn draw_panel_context_menu(&mut self, ctx: &egui::Context) { @@ -518,8 +586,10 @@ impl EskinDesktopApp { self.connection.disconnect(); self.pressure_matrix.fill([0.0, 0.0]); self.hand_pressure.clear(); + self.last_sample_at = None; self.force_estimator.reset(); self.latest_spatial_force = None; + self.latest_hand_spatial_forces = [None; HAND_FINGERTIP_COUNT]; self.spatial_force_panel_force = None; self.signal_history.clear(); self.hand_signal_histories @@ -538,6 +608,60 @@ impl EskinDesktopApp { } } +fn hand_image_pixel_to_screen(rect: egui::Rect, point_px: [f32; 2]) -> egui::Pos2 { + let image_uv = egui::vec2( + point_px[0] / HAND_IMAGE_SIZE_PX[0].max(1.0), + point_px[1] / HAND_IMAGE_SIZE_PX[1].max(1.0), + ); + let viewport_aspect = rect.width() / rect.height().max(1.0); + let image_aspect = HAND_IMAGE_SIZE_PX[0] / HAND_IMAGE_SIZE_PX[1].max(1.0); + let mut screen_uv = image_uv; + + if viewport_aspect > image_aspect { + let image_width = image_aspect / viewport_aspect; + screen_uv.x = image_uv.x * image_width + (1.0 - image_width) * 0.5; + } else { + let image_height = viewport_aspect / image_aspect; + screen_uv.y = image_uv.y * image_height + (1.0 - image_height) * 0.5; + } + + egui::pos2( + rect.left() + screen_uv.x * rect.width(), + rect.top() + screen_uv.y * rect.height(), + ) +} + +fn paint_spatial_force_arrow( + painter: &egui::Painter, + center: egui::Pos2, + force: HudSpatialForce, + base_length: f32, + magnitude_scale: f32, +) { + let magnitude = force.magnitude.clamp(0.0, 1.8); + let length = base_length + magnitude * magnitude_scale; + let angle = force.angle_deg.to_radians(); + let direction = egui::vec2(angle.cos(), angle.sin()); + let end = center + direction * length; + let side = egui::vec2(-direction.y, direction.x); + let head_length = (length * 0.24).clamp(8.0, 14.0); + let head_width = head_length * 0.5; + let color = egui::Color32::from_rgb(255, 196, 54); + let glow = egui::Color32::from_rgba_unmultiplied(255, 196, 54, 58); + + painter.line_segment([center, end], egui::Stroke::new(8.0, glow)); + painter.line_segment([center, end], egui::Stroke::new(2.4, color)); + painter.line_segment( + [end, end - direction * head_length + side * head_width], + egui::Stroke::new(2.4, color), + ); + painter.line_segment( + [end, end - direction * head_length - side * head_width], + egui::Stroke::new(2.4, color), + ); + painter.circle_filled(center, 3.8, color); +} + fn update_hand_signal_histories( histories: &mut [Vec; HAND_FORCE_PANEL_COUNT], raw_values: &[u32], @@ -718,6 +842,7 @@ impl eframe::App for EskinDesktopApp { self.live_rates.tick_render(stats.rx_frames); self.update_pressure_matrix(); + self.sync_disconnected_live_state(); self.draw_workspace(ui); self.draw_title_bar(ui, frame); self.draw_floating_panels(&ctx, stats); diff --git a/src/force.rs b/src/force.rs index 24b258b..4a7288e 100644 --- a/src/force.rs +++ b/src/force.rs @@ -1,6 +1,7 @@ use crate::serial_core::multi_dim_force::PztProcessor; -const FINGER_SAMPLE_COUNT: usize = 84; +pub const FINGER_SAMPLE_COUNT: usize = 84; +pub const HAND_FINGERTIP_COUNT: usize = 5; const MIN_TANGENTIAL_MAGNITUDE: f32 = 0.02; #[derive(Debug, Clone, Copy)] @@ -11,17 +12,22 @@ pub struct HudSpatialForce { pub struct ForceEstimatorState { pzt_processor: PztProcessor, + fingertip_processors: [PztProcessor; HAND_FINGERTIP_COUNT], } impl ForceEstimatorState { pub fn new() -> Self { Self { pzt_processor: PztProcessor::new(), + fingertip_processors: std::array::from_fn(|_| PztProcessor::new()), } } pub fn reset(&mut self) { self.pzt_processor.reset_baseline(); + self.fingertip_processors + .iter_mut() + .for_each(PztProcessor::reset_baseline); } pub fn analyze(&mut self, values: &[u32]) -> Option { @@ -40,6 +46,37 @@ impl ForceEstimatorState { magnitude: analysis.magnitude, }) } + + pub fn analyze_fingertips( + &mut self, + values: &[u32], + ) -> [Option; HAND_FINGERTIP_COUNT] { + let mut forces = [None; HAND_FINGERTIP_COUNT]; + + for (tip_index, processor) in self.fingertip_processors.iter_mut().enumerate() { + let start = tip_index * FINGER_SAMPLE_COUNT; + let end = start + FINGER_SAMPLE_COUNT; + let Some(segment) = values.get(start..end) else { + break; + }; + + let pzt_values = segment + .iter() + .map(|value| *value as f32) + .collect::>(); + + forces[tip_index] = processor + .get_pzt_analysis(&pzt_values) + .ok() + .filter(|analysis| analysis.magnitude > MIN_TANGENTIAL_MAGNITUDE) + .map(|analysis| HudSpatialForce { + angle_deg: analysis.angle_deg, + magnitude: analysis.magnitude, + }); + } + + forces + } } impl Default for ForceEstimatorState { diff --git a/src/main.rs b/src/main.rs index 96ac35d..c812a2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,12 +20,15 @@ use eframe::egui; fn main() -> eframe::Result<()> { env_logger::init(); + let window_icon = eframe::icon_data::from_png_bytes(include_bytes!("../res/128x128@2x.png")) + .expect("load window icon failed"); let options = eframe::NativeOptions { renderer: eframe::Renderer::Wgpu, viewport: egui::ViewportBuilder::default() .with_inner_size([1920.0, 1080.0]) .with_min_inner_size([1280.0, 720.0]) - .with_decorations(false), + .with_decorations(false) + .with_icon(window_icon), ..Default::default() }; diff --git a/src/render.rs b/src/render.rs index 022724c..cd6cdaf 100644 --- a/src/render.rs +++ b/src/render.rs @@ -73,14 +73,14 @@ const HAND_TIP_MATRICES: [HandTipMatrix; 5] = [ const HAND_PALM_CHIPS: [HandPalmChip; 2] = [ HandPalmChip { center_px: [530.0, 593.0], - size_px: [224.0, 68.0], + size_px: [258.0, 88.0], angle_rad: 0.12, rows: 5, cols: 14, }, HandPalmChip { center_px: [610.0, 778.0], - size_px: [60.0, 190.0], + size_px: [82.0, 228.0], angle_rad: 0.05, rows: 11, cols: 4, @@ -1203,8 +1203,7 @@ fn build_hand_dot_instances( // Lay out a rows x cols matrix in fingertip-local pixel space. let local_x = (col as f32 - cols as f32 / 2.0 + 0.5) / cols as f32 * tip.size_px[0]; - let local_y = (row as f32 - rows as f32 / 2.0 + 0.5) / rows as f32 - * tip.size_px[1] + let local_y = (row as f32 - rows as f32 / 2.0 + 0.5) / rows as f32 * tip.size_px[1] + HAND_TIP_DOT_LOCAL_Y_OFFSET_PX; // Rotate the local matrix so it follows the direction of the finger. @@ -1241,8 +1240,8 @@ fn build_hand_palm_dot_instances( for (chip_index, chip) in HAND_PALM_CHIPS.into_iter().enumerate() { let cos = chip.angle_rad.cos(); let sin = chip.angle_rad.sin(); - // Leave a bevel around the chip so the matrix reads as embedded pixels. - let active_size = [chip.size_px[0] * 0.72, chip.size_px[1] * 0.76]; + // Palm films can float slightly beyond the hand artwork, similar to fingertip membranes. + let active_size = [chip.size_px[0] * 0.86, chip.size_px[1] * 0.86]; for row in 0..chip.rows { for col in 0..chip.cols { @@ -1278,13 +1277,7 @@ fn build_hand_palm_dot_instances( instances } -fn hand_palm_pressure_index( - chip_index: usize, - row: u32, - col: u32, - rows: u32, - cols: u32, -) -> usize { +fn hand_palm_pressure_index(chip_index: usize, row: u32, col: u32, rows: u32, cols: u32) -> usize { if chip_index == 0 { (col * rows + (rows - 1 - row)) as usize } else { diff --git a/src/ui.rs b/src/ui.rs index 0377b65..a1de890 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -413,8 +413,11 @@ pub fn draw_config_panel( .order(egui::Order::Foreground) .show(ctx, |ui| { ui.set_min_size(bar_rect.size()); - ui.painter() - .rect_filled(ui.max_rect(), egui::CornerRadius::ZERO, ONE_DARK_PRO.panel_deep); + ui.painter().rect_filled( + ui.max_rect(), + egui::CornerRadius::ZERO, + ONE_DARK_PRO.panel_deep, + ); ui.painter().line_segment( [ui.max_rect().left_top(), ui.max_rect().right_top()], egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft), @@ -426,93 +429,93 @@ pub fn draw_config_panel( ui.add_space(5.0); ui.horizontal(|ui| { - ui.add_space(8.0); - ui.spacing_mut().item_spacing = egui::vec2(8.0, 0.0); - config.mode = SerialMode::Hand; - config.baud_rate = SerialMode::Hand.baud_rate(); + ui.add_space(8.0); + ui.spacing_mut().item_spacing = egui::vec2(8.0, 0.0); + config.mode = SerialMode::Hand; + config.baud_rate = SerialMode::Hand.baud_rate(); - ui.colored_label(dim_text(), "配置面板"); - ui.add_space(8.0); - ui.colored_label(dim_text(), "模式"); - ui.add_sized( - egui::vec2(102.0, 28.0), - style::mode_button("手掌模块", true), - ); + ui.colored_label(dim_text(), "配置面板"); + ui.add_space(8.0); + ui.colored_label(dim_text(), "模式"); + ui.add_sized( + egui::vec2(102.0, 28.0), + style::mode_button("手掌模块", true), + ); - ui.add_space(12.0); - ui.label(style::field_label("端口")); - egui::ComboBox::from_id_salt("config_top_bar_ports") - .width(180.0) - .selected_text(if config.port.is_empty() { - "无可用串口".to_owned() - } else { - config.port.clone() - }) - .show_ui(ui, |ui| { - for port in &config.available_ports { - ui.selectable_value(&mut config.port, port.clone(), port.as_str()); - } - }); - - if ui - .add(style::icon_button( - egui::RichText::new("⟳").color(ONE_DARK_PRO.text).size(16.0), - METRICS.icon_button, - )) - .on_hover_text("刷新串口") - .clicked() - { - refresh_config_ports(config); - } - - ui.add_space(10.0); - ui.label(style::value_text(format!("波特率 {}", config.baud_rate))); - ui.add_space(10.0); - ui.colored_label(dim_text(), "状态"); - ui.colored_label( - connection_status_color(conn_state), - connection_status_text(conn_state), - ); - - ui.add_space(8.0); - let is_connected = matches!( - conn_state, - ConnectionState::Connected | ConnectionState::Streaming - ); - let button_text = if is_connected { "断开" } else { "连接" }; - if ui - .add_sized( - egui::vec2(118.0, 28.0), - if is_connected { - style::danger_button(button_text) - } else { - style::primary_button(button_text) - }, - ) - .clicked() - { - if is_connected { - connection.disconnect(); - } else if !config.port.is_empty() { - connection.connect( - &config.port, - 12, - 7, - config.mode.baud_rate(), - config.mode.protocol(), - recorder.clone(), - ); + ui.add_space(12.0); + ui.label(style::field_label("端口")); + egui::ComboBox::from_id_salt("config_top_bar_ports") + .width(180.0) + .selected_text(if config.port.is_empty() { + "无可用串口".to_owned() + } else { + config.port.clone() + }) + .show_ui(ui, |ui| { + for port in &config.available_ports { + ui.selectable_value(&mut config.port, port.clone(), port.as_str()); } - } - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.label(style::value_text(format!("{render_rate_hz:.1} Hz"))); - ui.colored_label(dim_text(), "画面刷新率"); - ui.add_space(18.0); - ui.label(style::value_text(format!("{sample_rate_hz:.1} Hz"))); - ui.colored_label(dim_text(), "采样率"); }); + + if ui + .add(style::icon_button( + egui::RichText::new("⟳").color(ONE_DARK_PRO.text).size(16.0), + METRICS.icon_button, + )) + .on_hover_text("刷新串口") + .clicked() + { + refresh_config_ports(config); + } + + ui.add_space(10.0); + ui.label(style::value_text(format!("波特率 {}", config.baud_rate))); + ui.add_space(10.0); + ui.colored_label(dim_text(), "状态"); + ui.colored_label( + connection_status_color(conn_state), + connection_status_text(conn_state), + ); + + ui.add_space(8.0); + let is_connected = matches!( + conn_state, + ConnectionState::Connected | ConnectionState::Streaming + ); + let button_text = if is_connected { "断开" } else { "连接" }; + if ui + .add_sized( + egui::vec2(118.0, 28.0), + if is_connected { + style::danger_button(button_text) + } else { + style::primary_button(button_text) + }, + ) + .clicked() + { + if is_connected { + connection.disconnect(); + } else if !config.port.is_empty() { + connection.connect( + &config.port, + 12, + 7, + config.mode.baud_rate(), + config.mode.protocol(), + recorder.clone(), + ); + } + } + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label(style::value_text(format!("{render_rate_hz:.1} Hz"))); + ui.colored_label(dim_text(), "画面刷新率"); + ui.add_space(18.0); + ui.label(style::value_text(format!("{sample_rate_hz:.1} Hz"))); + ui.colored_label(dim_text(), "采样率"); }); + }); }); changed_mode @@ -954,14 +957,17 @@ pub fn draw_stats_panel( const FORCE_CHART_MAX_N: f32 = 25.6; const FORCE_CHART_SAMPLE_SECONDS: f32 = 0.1; const FORCE_PANEL_ACTIVE_TAIL: usize = 8; +const FORCE_PANEL_EXIT_DELAY_SECONDS: f64 = 3.0; -const HAND_FORCE_PANEL_MIN_WIDTH: f32 = 220.0; -const HAND_FORCE_PANEL_MAX_WIDTH: f32 = 500.0; -const HAND_FORCE_PANEL_MIN_HEIGHT: f32 = 104.0; -const HAND_FORCE_PANEL_MAX_HEIGHT: f32 = 190.0; -const HAND_FORCE_PANEL_GAP: f32 = 12.0; +const HAND_FORCE_PANEL_MIN_WIDTH: f32 = 160.0; +const HAND_FORCE_PANEL_MAX_WIDTH: f32 = 560.0; +const HAND_FORCE_PANEL_MIN_HEIGHT: f32 = 72.0; +const HAND_FORCE_PANEL_MAX_HEIGHT: f32 = 230.0; +const HAND_FORCE_PANEL_GAP: f32 = 20.0; const HAND_FORCE_PANEL_SIDE_MARGIN: f32 = 24.0; const HAND_FORCE_PANEL_VERTICAL_MARGIN: f32 = 28.0; +const HAND_FORCE_CENTER_MIN_WIDTH: f32 = 420.0; +const HAND_FORCE_CENTER_MAX_WIDTH: f32 = 820.0; const HAND_FORCE_PANEL_TITLES: [(&str, &str); 7] = [ ("T1", "拇指"), ("T2", "食指"), @@ -981,15 +987,14 @@ fn has_recent_resultant_force(values: &[f32]) -> bool { } pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[Vec]) { - let hand_active = histories - .iter() - .any(|history| has_recent_resultant_force(history)); - let target_visible = visible && hand_active; let screen = ctx.content_rect(); let side_margin = HAND_FORCE_PANEL_SIDE_MARGIN.min((screen.width() * 0.025).max(10.0)); let vertical_margin = HAND_FORCE_PANEL_VERTICAL_MARGIN.min((screen.height() * 0.04).max(10.0)); - let gap = HAND_FORCE_PANEL_GAP.min((screen.height() * 0.018).max(6.0)); - let side_width = ((screen.width() - side_margin * 4.0) * 0.25).max(0.0); + let gap = HAND_FORCE_PANEL_GAP.min((screen.height() * 0.024).max(12.0)); + let reserved_center_width = (screen.width() * 0.42) + .clamp(HAND_FORCE_CENTER_MIN_WIDTH, HAND_FORCE_CENTER_MAX_WIDTH) + .min((screen.width() - side_margin * 2.0).max(0.0)); + let side_width = ((screen.width() - reserved_center_width - side_margin * 2.0) * 0.5).max(0.0); let panel_width = side_width .min(HAND_FORCE_PANEL_MAX_WIDTH) .max(HAND_FORCE_PANEL_MIN_WIDTH.min(side_width)); @@ -997,17 +1002,16 @@ pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[V let right_x = screen.right() - side_margin - panel_width; let left_count = 4usize; let right_count = HAND_FORCE_PANEL_TITLES.len() - left_count; - let available_height = - (screen.height() - layout::TITLE_BAR_HEIGHT - vertical_margin * 2.0).max(0.0); + let chrome_height = layout::TITLE_BAR_HEIGHT + layout::CONFIG_BAR_HEIGHT; + let available_height = (screen.height() - chrome_height - vertical_margin * 2.0).max(0.0); let panel_height = ((available_height - (left_count - 1) as f32 * gap) / left_count as f32) - .clamp( - HAND_FORCE_PANEL_MIN_HEIGHT.min(available_height), - HAND_FORCE_PANEL_MAX_HEIGHT, - ); + .max(0.0) + .min(HAND_FORCE_PANEL_MAX_HEIGHT) + .max(HAND_FORCE_PANEL_MIN_HEIGHT.min(available_height / left_count as f32)); let left_height = left_count as f32 * panel_height + (left_count - 1) as f32 * gap; let right_height = right_count as f32 * panel_height + (right_count.saturating_sub(1)) as f32 * gap; - let min_top = screen.top() + layout::TITLE_BAR_HEIGHT + vertical_margin; + let min_top = screen.top() + chrome_height + vertical_margin; let left_top = (screen.center().y - left_height * 0.5).max(min_top); let right_top = (screen.center().y - right_height * 0.5).max(min_top); @@ -1029,7 +1033,8 @@ pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[V egui::Id::new(format!("hand_force_panel_{index}")), egui::pos2(target_x, target_y), egui::vec2(panel_width, panel_height), - target_visible, + index < left_count, + visible, code, title, history, @@ -1042,12 +1047,35 @@ fn draw_force_chart_area( id: egui::Id, target_pos: egui::Pos2, size: egui::Vec2, + from_left: bool, target_visible: bool, code: &'static str, title: &'static str, values: &[f32], ) { - let anim = ctx.animate_bool(id.with("enter"), target_visible); + let now = ctx.input(|input| input.time); + let active = target_visible && has_recent_resultant_force(values); + let state_id = id.with("active_state"); + let mut last_active_at = ctx.data_mut(|data| data.get_temp::(state_id)); + if active { + last_active_at = Some(now); + ctx.data_mut(|data| data.insert_temp(state_id, now)); + } else if !target_visible { + last_active_at = None; + ctx.data_mut(|data| data.remove::(state_id)); + } + + let target_visible = + last_active_at.is_some_and(|active_at| now - active_at <= FORCE_PANEL_EXIT_DELAY_SECONDS); + let anim_id = id.with("enter"); + let anim_seeded_id = id.with("enter_seeded"); + let anim_seeded = ctx.data_mut(|data| data.get_temp::(anim_seeded_id).unwrap_or(false)); + if target_visible && !anim_seeded { + ctx.animate_bool(anim_id, false); + ctx.data_mut(|data| data.insert_temp(anim_seeded_id, true)); + } + + let anim = ctx.animate_bool(anim_id, target_visible); if anim <= 0.01 { return; @@ -1059,20 +1087,27 @@ fn draw_force_chart_area( } let eased = ease_in_out(anim); - let hidden_y = ctx.content_rect().bottom() + HAND_FORCE_PANEL_GAP; - let y = egui::lerp(hidden_y..=target_pos.y, eased); + let hidden_x = if from_left { + ctx.content_rect().left() - size.x - HAND_FORCE_PANEL_GAP + } else { + ctx.content_rect().right() + HAND_FORCE_PANEL_GAP + }; + let x = egui::lerp(hidden_x..=target_pos.x, eased); egui::Area::new(id) - .fixed_pos(egui::pos2(target_pos.x, y)) + .fixed_pos(egui::pos2(x, target_pos.y)) .constrain_to(viewport_panel_rect(ctx)) .order(egui::Order::Foreground) .show(ctx, |ui| { panel_frame(ctx).show(ui, |ui| { + let padding = METRICS.panel_padding as f32; + let inner_width = (size.x - padding * 2.0).max(80.0); + let inner_height = (size.y - padding * 2.0).max(48.0); ui.spacing_mut().item_spacing = egui::vec2(METRICS.item_gap, 6.0); - ui.set_min_width(size.x); - ui.set_max_width(size.x); - ui.set_min_height(size.y); - draw_force_chart_panel_contents(ui, code, title, values, size.y); + ui.set_min_width(inner_width); + ui.set_max_width(inner_width); + ui.set_min_height(inner_height); + draw_force_chart_panel_contents(ui, code, title, values, inner_height); }); }); } @@ -1095,7 +1130,7 @@ fn draw_force_chart_panel_contents( }); ui.add_space(6.0); - let chart_height = (content_height - 32.0).max(90.0); + let chart_height = (content_height - 32.0).max(36.0); paint_resultant_force_chart(ui, values, chart_height); } @@ -1103,7 +1138,7 @@ fn paint_resultant_force_chart(ui: &mut egui::Ui, values: &[f32], chart_height: const CHART_POINTS: usize = 42; const CHART_WINDOW_SECONDS: f32 = CHART_POINTS as f32 * FORCE_CHART_SAMPLE_SECONDS; - let width = ui.available_width().max(180.0); + let width = ui.available_width().max(80.0); let (rect, _) = ui.allocate_exact_size(egui::vec2(width, chart_height), egui::Sense::hover()); let painter = ui.painter_at(rect); let radius = egui::CornerRadius::same(5); @@ -1358,8 +1393,10 @@ fn draw_pinned_panel( let panel_width = responsive_panel_width(ctx, preferred_width, min_width); let max_height = responsive_panel_height(ctx, ctx.content_rect().height(), 180.0); let viewport = viewport_panel_rect(ctx); - let x = (viewport.center().x - panel_width * 0.5) - .clamp(viewport.left(), (viewport.right() - panel_width).max(viewport.left())); + let x = (viewport.center().x - panel_width * 0.5).clamp( + viewport.left(), + (viewport.right() - panel_width).max(viewport.left()), + ); let y = viewport.top() + layout::TITLE_BAR_HEIGHT + PANEL_VIEWPORT_MARGIN; let pos = egui::pos2(x, y); diff --git a/static/wgsl/shader.wgsl b/static/wgsl/shader.wgsl index d503185..c3e7745 100644 --- a/static/wgsl/shader.wgsl +++ b/static/wgsl/shader.wgsl @@ -478,8 +478,8 @@ fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f { let bevel_highlight = vec3f(0.48, 1.00, 1.00); let bevel_dark_color = vec3f(0.004, 0.035, 0.060); let dot_color = vec3f(0.08, 0.48, 0.58); - let pressure_green = vec3f(0.01, 0.78, 0.23); - let pressure_hot_color = vec3f(0.20, 1.00, 0.42); + let pressure_color = dot_color; + let pressure_hot_color = dot_color; let color = extrusion_color * extrusion * 0.90 + extrusion_edge_color * extrusion * halo_shape * 0.32 @@ -492,7 +492,7 @@ fn fs_hand_membrane(in: HandMembraneVertexOutput) -> @location(0) vec4f { + bevel_highlight * shell_sheen * 0.28 + rim_color * halo * 0.16 + dot_color * dots * 0.68 - + pressure_green * dots * pressure * 0.82 + + pressure_color * dots * pressure * 0.82 + pressure_hot_color * dots * pressure_hot * 0.90; let alpha = clamp( @@ -583,13 +583,13 @@ fn vs_hand_palm_chip(vertex: DotVertexInput, instance: DotInstanceInput) -> Hand @fragment fn fs_hand_palm_chip(in: HandPalmChipVertexOutput) -> @location(0) vec4f { // Inactive palm matrix: only independent dots, with no filled patch underneath. - let panel = rounded_rect_alpha(in.local, 0.11, 0.040); - let inner = rounded_rect_alpha(in.local * vec2f(1.08, 1.08), 0.09, 0.052); + let panel = rounded_rect_alpha(in.local, 0.16, 0.050); + let inner = rounded_rect_alpha(in.local * vec2f(1.03, 1.03), 0.13, 0.062); let uv = clamp(in.local * 0.5 + vec2f(0.5, 0.5), vec2f(0.0, 0.0), vec2f(1.0, 1.0)); let patch_mask = smoothstep(0.0, 0.36, inner) * panel; - let grid = max(in.grid * 1.70, vec2f(1.0, 1.0)); + let grid = max(in.grid * 1.42, vec2f(1.0, 1.0)); let cell_uv = fract(uv * grid) - vec2f(0.5, 0.5); let dot_dist = length(cell_uv); let dot_aa = max(fwidth(dot_dist) * 1.5, 0.008); @@ -607,8 +607,8 @@ fn fs_hand_palm_chip(in: HandPalmChipVertexOutput) -> @location(0) vec4f { let pressure_hot = (1.0 - smoothstep(0.02, 0.14, pressure_dist)) * patch_mask; let idle_dot_color = vec3f(0.18, 0.32, 0.38); - let active_dot_color = vec3f(0.02, 0.88, 0.34); - let hot_dot_color = vec3f(0.28, 1.00, 0.52); + let active_dot_color = idle_dot_color; + let hot_dot_color = idle_dot_color; let dot_color = mix(idle_dot_color, active_dot_color, pressure); @@ -633,7 +633,7 @@ fn vs_hand_palm_dot(vertex: DotVertexInput, instance: DotInstanceInput) -> DotVe // Palm chip pixels are deliberately smaller than fingertip beads so they read as a chip matrix. let center = hand_image_uv_to_clip(instance.world_position.xy); - let pixel_size = u.glyph.x * mix(0.13, 0.25, shaped); + let pixel_size = u.glyph.x * mix(0.16, 0.30, shaped); let ndc_offset = vertex.local * vec2f(pixel_size / u.viewport.x, pixel_size / u.viewport.y) * 2.0; var out: DotVertexOutput;