From 094637b0a4ef10ec8e79c5535473555337f5c025 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 27 Jun 2026 19:48:14 -0500 Subject: [PATCH] Editor: rework to the original Win32 RIO control-panel layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reworked the profile/mapping editor to mirror the original unfinished Win32 RIO driver's control-panel design (docs/Win32RIO, by FASA / Michel Lowrance) instead of the flat config-sheet grid. The wallpaper region positions are a VGA chroma-split display artifact, not the cockpit's logical shape. - RioJoy.Core.Editing.CockpitPanel: the functional layout — five MFD clusters, four board columns (Throttle/Secondary/Screen/Joystick-Hat), an encoder-gauge strip, and the two later-added 4x4 keypads (Internal/External). Places every address 0x00-0x47 / 0x50-0x6F exactly once (unit-tested). - Tray PanelView/PanelCanvas render the panel; ProfileEditorForm drives it. Buttons show label + assigned function (ButtonBinding.Describe -> CTL-N, JOY1, POV-U, Mse LB, RIO command names; unit-tested). Lamp shade is driven by the IsLit flag (not by whether a function is assigned); keypads are neutral blue with no Lit checkbox. Added an Unassign button next to Apply. - Removed the superseded sheet-grid UI (SheetView/SheetCanvas); SheetLayout + config-sheet.csv stay as reference data. docs/Win32RIO: the original driver (tasgame.sys, a 32-bit kernel HID minidriver that opened the serial port, set baud/8N1/DTR, and spoke the *identical* RIO protocol -- AnalogRequest/Reply, Button Pressed/Released, Check/Lamp/Reset/ Version -- validating our protocol port), oemsetup.inf, RemoteDriver.doc, and the extracted control-panel / game-controllers mockups. ~236 xUnit tests green. PLAN.md updated. Co-Authored-By: Claude Opus 4.8 --- docs/PLAN.md | 31 ++-- docs/Win32RIO/RemoteDriver.doc | Bin 0 -> 64000 bytes docs/Win32RIO/control-panel-mockup.png | Bin 0 -> 12796 bytes docs/Win32RIO/game-controllers-mockup.png | Bin 0 -> 9071 bytes docs/Win32RIO/oemsetup.inf | 133 +++++++++++++++++ docs/Win32RIO/rename | Bin 0 -> 16768 bytes docs/Win32RIO/tasgame.sys | Bin 0 -> 16768 bytes src/RioJoy.Core/Editing/CockpitPanel.cs | 109 ++++++++++++++ src/RioJoy.Core/Mapping/ButtonBinding.cs | 33 +++++ src/RioJoy.Tray/Editor/PanelCanvas.cs | 67 +++++++++ src/RioJoy.Tray/Editor/PanelView.cs | 135 ++++++++++++++++++ src/RioJoy.Tray/Editor/ProfileEditorForm.cs | 77 ++++++---- src/RioJoy.Tray/Editor/SheetCanvas.cs | 57 -------- src/RioJoy.Tray/Editor/SheetView.cs | 83 ----------- src/RioJoy.Tray/TrayApplicationContext.cs | 31 +--- .../Editing/CockpitPanelTests.cs | 68 +++++++++ .../Mapping/ButtonBindingTests.cs | 13 ++ 17 files changed, 625 insertions(+), 212 deletions(-) create mode 100644 docs/Win32RIO/RemoteDriver.doc create mode 100644 docs/Win32RIO/control-panel-mockup.png create mode 100644 docs/Win32RIO/game-controllers-mockup.png create mode 100644 docs/Win32RIO/oemsetup.inf create mode 100644 docs/Win32RIO/rename create mode 100644 docs/Win32RIO/tasgame.sys create mode 100644 src/RioJoy.Core/Editing/CockpitPanel.cs create mode 100644 src/RioJoy.Tray/Editor/PanelCanvas.cs create mode 100644 src/RioJoy.Tray/Editor/PanelView.cs delete mode 100644 src/RioJoy.Tray/Editor/SheetCanvas.cs delete mode 100644 src/RioJoy.Tray/Editor/SheetView.cs create mode 100644 tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs diff --git a/docs/PLAN.md b/docs/PLAN.md index d9c5809..fde7b8e 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -238,21 +238,22 @@ Replaces the legacy Google-Sheet → `.data` → GIMP → Script-Fu pipeline activation). The live `SystemParametersInfo` apply changes a user setting, so it is gated behind config and not exercised by tests. ⏳ Optional: restore the prior wallpaper when going dormant. -- **Mapping editor — first cut done ✅.** `RioJoy.Tray/Editor/ProfileEditorForm` - shows the cockpit as a scrollable, clickable grid that mirrors the legacy - authoring spreadsheet's **logical** layout — parsed from the sheet's CSV export - (`docs/reference/customBackground/config-sheet.csv`) by `SheetLayout`, coloured - by section. (The layout deliberately follows the sheet, **not** the wallpaper - region positions: the wallpaper drives 6 displays via VGA chroma-channel - splitting, so its label positions stack and don't reflect the physical cockpit.) - Clicking an address cell edits its label and action/modifiers/lamp; the `iRIO` - word is read/written through `ButtonBinding` ↔ `RioMapEntry.Create` (round-trip - tested) — no hex bit-twiddling. The value field is a **context-sensitive picker**: - keyboard actions pick a key by name (`KeyCatalog` VK names), joystick a Button N, - hat a direction, mouse/RIO-command the enum name; modifiers enable only for - keyboard. Opened from the tray ("Edit profile…"); Save persists the profile. - ⏳ Still to refine: grouping a button's two bank addresses (edit once → both), - and clone-from-existing. +- **Mapping editor — done ✅ (cockpit control-panel layout).** + `RioJoy.Tray/Editor/ProfileEditorForm` shows the cockpit as a clickable control + panel matching the **original Win32 RIO design** (docs/Win32RIO/, by FASA/Michel + Lowrance): five MFD clusters, four board columns (Throttle/Secondary/Screen/ + Joystick-Hat), an encoder-gauge strip, and the two later-added 4×4 keypads + (rendered without lamps). The layout (`RioJoy.Core.Editing.CockpitPanel`, which + places every address 0x00–0x47 / 0x50–0x6F exactly once, unit-tested) deliberately + follows the design, **not** the wallpaper positions (those are a VGA chroma-split + display artifact). Lamp buttons shade Off/assigned; keypads are neutral. Clicking + a button edits its label and action/modifiers/lamp; the `iRIO` word goes through + `ButtonBinding` ↔ `RioMapEntry.Create` — no hex. The value field is a + context-sensitive picker (keyboard key by name via `KeyCatalog`, joystick Button N, + hat direction, mouse/RIO-command enum); modifiers enable only for keyboard. + Opened from the tray ("Edit profile…"); Save persists the profile. + ⏳ Still to refine: live encoder gauges, showing each button's assigned key as a + caption, grouping a button's two bank addresses, and clone-from-existing. - The unified profile JSON supersedes both `RIO.ini` and the Google Sheet, with importers for each. - To confirm at Phase 7: target wallpaper resolution(s); static wallpaper vs. diff --git a/docs/Win32RIO/RemoteDriver.doc b/docs/Win32RIO/RemoteDriver.doc new file mode 100644 index 0000000000000000000000000000000000000000..e6d2bc97486df64afec4fc6490212b02c3461226 GIT binary patch literal 64000 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 literal 0 HcmV?d00001 diff --git a/docs/Win32RIO/control-panel-mockup.png b/docs/Win32RIO/control-panel-mockup.png new file mode 100644 index 0000000000000000000000000000000000000000..2b4720adb261cbf023561eb4f336c9804693eced GIT binary patch literal 12796 zcma)j2{=@3__rndHb}w{24jg9VUT3sGDJg|NZBIlPzgm@XR;;Phms`w&Pb@7P?)lf zZK#frJ!vTGchvj-x9j@;-*;WVac0i?Jooy$@8=*|V@-HCML8K577+_Fd`Y)A{p2t8QG&4IbcLHaz-<9!H8kxj$!11L15&C zL1g43G9rjfhbtg0{`u^xl* zm{sjDt2&HaR*hU%42&nNnorm?^VqcV*tB6hWjp?qO$SCk+X)!Y*mR$dYdIb2I34S_9P7E9>bdYR$Xw^h zT+R(#E-+qjoqxgQ3Zs$hLL>LZm)veJnz-GYxIJJrb9**(d$n+1g7J#m`xUp(D;}TM zJeObd_`c!sgVD-!rIqI@43Gx^dHmn<1ia-9Xyd)s#(Nz`JMRq`9lU`Zyfz-4-MLf#XWy$=um0?YkL*xT1D6>X4)K^7>YcecvXC7=p<{7wQ22C5LD@b3 zM)M(AQ$Kw`>OQAIzNCVs;(_l+jwmQA*2+JUw?4%wgv$RYe~?pfh4Fy(vC|5bAs?1* ztyOGJeY*5PbK|h8c4A|7$c-B=OCe#O0w_5dw=)NS-}3O7R<+%$S}2h6v})tbz9)83 zy{7y9mM2w~tPpH~4{Gs2k)q0#@=xdLpZ989Gvl-fe>p%4m;U)iQiVijRU?w$n5M5c ze{-ob#rL>)3|*fzLpF0LyESVEM1G_xmhvKUHJ|dYMOL3!oXGhFCYIAVtb%)!bMSMC zw=R@gtv|Ke{%Tc7FAS@pGV>k(e(90(oa%`Q1*_j(j*sa5j(jJ6eLfKKoa!02I(>`M zM_He!&Ds805VrW}IsJRfesiGMnXcipRoZfHPO0Tbar11k z&+YDFm#x|IQu=ilmpQTKFJdjrS>9XOSIX#_gDu+z= z??gE6e7{~EGNxNZPuwvpUHX-^7-t1Z6Jd@r3_A5k?dlWZ1@AG=6g&I+?qAu)43_N z`1?cHYX>!F11M*hf6cU9ID-Wk7A(Mhi387FI)*?;-X+JJIY-zC=w^#2$2dVbzX>XY zQB}Zmid7xU9vi=cSo@wqR`S*X5>L`{2#q&>=Il_+Xk0l|#M+w}H63PX)nh`1??^0z zSm2E^HGuv}6@cb(hj{Dd7JrBu(yS5n+T|lBKK?+ULG*Dm0@`J}Wr(ZWJW)ygDyrGeow~S)9dDPOWzk2X#$=3dF&7v;9r@93O+){qZ)&?s^0hthE8iqFMUSqt9#2%V3}btkqfH8Q0$wzQ)62Dw*D7F)X2F!v(;PjeS|MU!d1_!c9S^nDDS>;b`JeNOy^US{< z7gEOc_NHPO*I36ah`Tw!gYfM6`Q{+c+~K{?f=TA2w{52rBgm)0jFF<-d{@^-s)uFY z7WhCji6OzUXiVq#eX#D`gYIBph15)Th}M`3)l2g(*vf)Z1Or420?du) zZ%3Y#|NYe55Atfyd-_8#hV7);mcqZs;nUb51pF&j+AlwQNDnO!CF8!Z8LRTwzwr4; zR4Nfzeiz=C`Wdl}3S5GAajxv(*kypXyPP3_CYqAZ8?yjZfU zJ_=}ddp+}KlzPbU-NF36I?#x z`|X7KffwCu-B-+K78+X?b3?-JuZY*Q>d|fx`@EDoTR+FIULOuq9yMy<^`dR%cKTP$ zTW>G`Vs!wUPp)ZKrVyvQtY8sxcKNo8i91(I2S;sr)O@r>M3#jta{qeVB$i>l>2V`!t|8f56eu-AytM91_4EhvN$X|+b zIE^GArmG9&i(-_bZzBQlJy=gspPQM2QsT{`~et z;@~l%w-bq<0tKcm>S?wrqy4I}Iv4jV5gH#{D<|JSLt8;in*|0yc6>;%R%Wc_y#+~o zES{)casWf<9B6Yd&JrBFu5JyxKMNsQORVmbosp6xR`JzMNW#LD#jQSUDnxHFzxO!!1Cevy1+_LeqC&*p(G1a_}c zBw*vo4C>KFMvLG*zgOWXjwLvCsNONnGN zAL9fEJz7dvOYG@o+OEOXJ3%zpwcyQ033o4-SI)z4T@f$J7wTiX>~1)xwkqd&AnuUq z!{i7BzuEI~%MYHBHOay!#KGafpqeYbv6f;sRvCH>kliG1D=9ceZ@+(GNk`LFvzw8`auH(gXUgO}AoODd# z3yr(v&mwy&fQBI4#qa*dfJ!>K*C|mKnsx#nf)zF@5{c?DyrKz;2 zf#!detf0z%5^2>ymHanI{V$=0JSx~e#7}5#&3mNC`+mWZ%-j2|2KFuYrz|S-;}wA^_K;D|lJ{m7Adf-Esxi%Red3y! zr$SeT-HZA*qb$l>3ld#XUz#Im>Z*m9=fRz?V1x8>9#QOAq@-WI)sGO9eHTvHjFQK7 ztmMkB9lXd3_=sv>cmzs4kqAuxjN?iAwYt*d$g@afrr9!{q?P7qB7e$Gv-Qlkl5f_I zX@us7gvd0j&qQ8wzSH{3Vs3oo&XdB>`*$$LO3M$Gs`~iE?7!oMU|Pal$d&0kK2mcDbR#JqNbb7b_2ot(|=wAHps6CnO?v zbJIWYdkdBt&drMRcPJAqERcrXCbUd98OlNaM+Y(DoPxU<6-FUgYKf@#g{Te(c#gmR z<%aupx92!0wFiK+WH@Gb;~`gea3gJ{`keb5GA$7I(JX&hWJCkdYve=w-A#@ngU+?C zr9q9RBEf+NOpqOuMx?gk-ydkQ?7)UxnPN(KR?CI2pLCtCTY+QaZ;a}Zk}8KF$MP2s z>Xx?0hTS|X3{l|H*r$)cC4wo^qFJDOKm6vN6 z=kA>T+tk7dvGZpnl3a-fZqANk@)7J%LaqQx^&mj#B95LEIr&FIdZbGq2B)jiH1rCj z^9KXYmQf$Irn=s=LDaKAFD~%Pl!Xmcoh7bJR=6D|?FD_KG5i*!*Gxf#>gfu11fm{S z9vt_xC<&yWurU;8g6igRiOcFLWC@Mlwt;duuK3F;&3mH}8i)N6^%o)58BZ_G^AnOP zAcK*{9!WndaPnp?qW+W!_t<_|T`&{g)$U>{XoMX+CfzC!)8XVK&)mk47qxKgpNT^t zze9C+?})uyx5AkVZqc&5Nt!_5?!4kKhHn35Qu>5N%eC>S2#pAIyTU4B6kXQbv1qwL zHN!tLx40rp31fE>A$=;4!Cuoo6HC1pp&OxX!q9&%+QJdlpCyewwzF;}0j+btdGIs6N^h1c77g1l7hWGv5JuXj6oy$uwG2aJvrIO2`_q zuIH&wzhF>LTLE>!_I?Y+^TfpkRAMk-JYh{#@6>qk0l-X8mA*HX^6^i95?5 z$2m8C#AIs0B3AP#G2m<}S*=ae(Blseha;KoOv0mAy+g)%!jEcb(Pl zk^3kFTOO;9X6(`r#ZOx+-1z%2K zNq7aWDxjHr6sTFcE1IJ>r)jGE{uLdGO#+F6Ad3Kc(I4dp?MZ#6 zX(t}Tex-PkNGAFYARxnoz)CHy-RisumUMq(6U!g-SeG*B;Nf)F|&`*sM$aQ;F-Bkmma$F{#D~1X5C6F1fGJ-75 z$}mS+d~*srF!>{HX3SabecZ^VI_UeZn3rI>gquKWz;QuNKzN2M<+nlK%^EDe_^UdU z)Dvnz|HL;&=gxs$n)5FflK!VjBlUMJ`%eWW>4ygSCwKrZ7o`s~e0U--KjSn^Nh}y`z^3%zeGK5g{-xIsJ{kf#cmp>eaAq$q(bkrQAH5sbjY&cnA1C@= z3lhdi0R7asxDwz3e#5pVO%yCR3vFt?x7L^PV+QF>SMP!x4I#=;C8j^Pet?T2RDvh( zF2VeRJR^8^xmz56lH6$8?A#SN)V$pEwbdy9A!iG9>Xc;)!(zM%^(#9=iav_4^Lf5` z@>|dl-?K$L&I>L?k*N#s%2U8})}+~klSufkcR_`nuZhZr0?yHA6kUS1;9N^F!Jh^N z?Dxt+6#zomd!oo3t{M7It@@_JvR8@2bhoh}(97q0+FT+Y#}q=sGE3PW^F%4a3e%pVI}M;|0tlta~E^d z+kIKHL`2dr7Ubv*QQ~bjQ_GHFM`3AtG!LR+U=kRF9=IkUT~Zp%24u-%X}`_uxmhR! zcJb1bFf1bN2%zs=Bey3;CBipJtX+zUxRN6XPFj!*jtBf3zX$VRz7g z9El}&$?oO!{fbA9!g=;T&itpe|6f@1e;gvyV>?6%1{;EZGc(+rC>1o3=@-4Hw4+au zOc*Z_LZpRB{mnC_@fayXniNt*n%H=yg*P~ zD28~MjYaM{{LIU|$FliJxWDYk(44@}-0PKQ>Q_5_F0D){^CHqbU=*Pd=9?5TH zvd)!#JRVSs_?lIMRz5|1mIj7{LSw{zQ5odW#@||%Kp!F5XEVy#C|R@SVn#}QpGv>i zgrY&GULFE^CTK5W{>loU`QU6E*}Srq-+AC#HiJeuSKil7?oD6AwSa)}z4c8=f#wK5pBS%xR_&Ui55p!*1iY|qSNfr8I)`3Co$P6WU zm8lLHimL;3p>-eCo_#kq!0;RfHfcnC(E0MFp-maLlMF@V!hx_D2;E222ERH;KJjpm zN2>(C=L(GG5tz;~mf7Y_$=!@Q80k(F?9n|$;94${rWUvH3;XW2c+?H>cpZxV^KuDc zCmhW|XA$Ot)1yULic~;)X~rg z&aL~%Kb%?{_$|!zPn=WJZvF|W9qOG6a?xK`ow-0aFfDv~5yRV)EeN_>;Y}V|OL~2} z7MK3P@NRE#rMSnSp%x&XNR1uPjKcIS>`#p|CN(@p#(eWljBaO1pKAj^1@x1bjq|HE zpY5RNXbTkfQ|*H@TG1&O19gH^I7T7%LX4x}z+L`1T%zd5FQ9^%q@OFKrHV@VQrVQ< z#3(9&p0r&%FN1B`>LdE3tBzvcMRyzH!_p*GQZcpjqls_4Ck(IXXcXUKV76a(lq~11qvcB7lh5N$)keff*3O3CDSqJ_-5k4PgTu0AJYpI1} znBkJ?1){!=9-F%H+HJd!wDEJz0N>A%QeZ7Yh<%^65x;)+IXpyv6qfzSO}IBy7RUol z$-qJCYoL=Hwx(R4o7nh~DlMEgSkYT|X@DN7F&msS#%CDv>Cyd{Lp3j&8?N%BN9?6~ zLk2d9szPwfDM3hz@hQLOO3~uWsoF8$X*E7zN=#N6uhOE)i3XHC^~3ofh&X3`M9^Ny zhOuq3i3NxXe!~j|=gH;5U6eZOL<=JlJ~Ek@HCDU|#}~FjId3VfT(=(S*M4hMr!AC;iSPr=&)qrfDJ=%61JooA6jWX@r=#8@w*gl2wWA??x zs);xxT1?|S(Pgzh${shMuhksV*c!`zk<0k)Er5e)n^3c!YQO-Ti@kRCHrJeZMtkwp zcGqiXgT(nnE|f}%gvLhvgAkJvXSJV^`xX-?6ojoBZkBNZSyI?YB`r0$H8<79@L?k& zDYK|y=h*a#+s4@i*7gg=p<p^)rXEZo(3jjavyP)oI0udisQ^+@}iI}DMkt0CeU9ak4 zj1mw*V4Aq3VoADTJo}xz8aWSkpP%>|jIRUpe-zoaF<77<LjG*6hcnFGAV%XAe8Ss(ooJ$V#y@V681 zMPIzj=>whb#!>!yZC^=5ec2PsG|;MBN+| zQUvZD8ld!0{Sj3nR=yKeONkc4{_H=BCY*Rx1<@=+9pJm>Gg6B;not?K0fcYjX1)Fu zYwa9TAxG5|b@e*+{K($9MgER!Ajx8#|(4|q&2{ffPh8L z8QPf=&MM~$7*x$fsUEF#t4{vsr(oOqjPs|I9l=wV+<*9%UJFtR4>$IS& zj5P(oZhg{mV+CR8MQ-%0&)Q4ckz2a&kQoJH-AR`ctB>3gLO29mi__9oMoi!De!jA5 zcWL5iX5bU?$C?+qMbW?yLgh(F;KOm>#NZ0VbbQHjyXM&|{fmS?43`W=r|4*Zn}cz- z*c}V)m7f?$&V(w1#uB!1dVbx`^qUXc-ztFbmiJ`ImVKm}K4@zbDn$8sR0F>egBeHj z;<;q~ZvA}xadXxm6xmlOf*6eNz~6T3u6^!*shD>Tuq2zx18;^7OwWrGWhhnArbor5 z(by}e;_sF42h`%CK4ujM)gjm29cej$Az#I}Z14SmF)A$_Bm3mF0ZFN<&75RCa(3ls z`=kMBR;7&}jm;xA?u)XiE}KspHTb#@O%ZSi;Pz(?_v97SR)721goFYjzT;$HJxkv* z*26z3`^j_MK4nTgW7!xkpT3_7 zpRt=42qi%IuHKT=-p7*~bju{Gq9r~X5mBi=H=xM-(F|9@SdvJDfHpNR)&0CSQxzVT z#;BtMs8JIZBi6W^FN@~BtV)9o#u=S`p3Ya!bY!Yc7~H?1&UzN5@~dDjwnPD0U9}p7 zGIkqJC8wBB=kJnb0N#2@3jd%r9Th%_Gp-_(ysMGW@SmDN5&}T@DV7>S;mvZQCrPcv z0uIUJZA6YFHCH;hU!9^kW&gyoxLp(W@aj3PrTB2NWe9FZJ^%%nG&Maop`NuzXm)!} zsQ1`KVc3A=!4k6t+{#j-m;ouT^u4rO(4T({6O6Nqg|P@ zmYV)4322kzNormWuq-+LdVwYG$Wn*5-(E1kA<5xHKt~*Td-=BA3d08zq`IFo)Zb$s z)b&0SVyt!NyfX9KZ8P~+XL5+|3PH@BZ?;#%Tz9`aozZ|~pc{UvE8bj9TGgst^u#Q! zX)!K~tiCk)_L!rYB91=a->pEfHBlB3O!YR5KOs6~LOSckeKi%-^r%dfxGr&iBC_Hp zPWE8=j;v|aIL{sYL$buNm0M%6g6MLGWE7BkH*tKJo-Bwi%}1q`-?uBtx;*jlYN>g` z{`q>JN_-wcuvx6uQ!MQv=C^4NXy!9x>RxPeI$jzpdf_mVtE9WQ+{9GrHGW<4Ji&>8 z0Wzb=D*B3QK+4f(+DTH)*xu>K0tIC;U*bym-#PLAuE(|WdpbQk&jf5aYGvA6nvg_~ z=Z#=4>R`*i6)6#hh=RAZ6@CZv{$6X3_t8964YYNL21U6xOg_F@KTN0|!3<~cQcL(X zX2joZWweshH;1L4`WkB(EfG%S+bm&IUX^>VCRpV7=g*j@Ewq30A>sQKH{FLQkX&)+(w+ge#Jl&#| z)7k>!X~f_=o-$>JDM~5*_ImhL(5!Rskc6=LMf{7Id-V3#JV3R5lw=vFL#4-Cki-2{ zq`N72`q7hfA@@VOVlabyYfeCsnsds8;*z@3cFFq^qu)*3oQ7l1csqNJPW@?FMX$I!@+gSZagb-zQzc~Ba zIDC^7@ZMZ48z#$WN5sG%gpxp3(vA-bY8trf^%9wT%IBVZK3tMLr(Z= zR>;nTq|Wz!$`}6x{kvXNTiP=GQaaOFKe~L9n#o|zbhYlgmVo$;5e#7NX8F}iRR0D%ZVwGl=iq(yx7y0F$dgBx;il7XG+?q@_cJ9RCOPe&}!u4kvJKS z3DHrBBae86DOQx9^0a1u+>;;VSu zR{M0Of99g|QArh8>-ETk;_CKBu=uOX9VTS4;#5yGRzzZ~>Ck$vI_kB>UWb6dG*36n z<3K9zfj%7DvpsUo?R@=Sr0@Sk-{qR;Nz(BIi!j~S)cmZuTfLmpLHwQxWk5n=wdM7k z8{MLF`01v2+o^%T!3i1|ujY>^C=Hd1e~{cnypr-XFAEM>N#rAR4BfE}5jYV{lgzX* zO}Tp~nC3`Op?)H4)B1U_Pu}8#* z1~aJapZ72|yl~PQ*Nl5`TM?m~pUcu! z9a4Mk?Bo80jmyUec$C2JBTDz@pjKiN)CSgVK`=VW=Mq*N|nVnu6aP5X!~S>c0}2jw?(kHa1Wynf z)l-c&MlPG4#8)bbd7`jIA4CXqeaBrrG)8X@P(9a2ij3U13k>#~;fHCRvG%^sSv&|0 zSjEK0WQ*=Ly6><%!Kd?$Q=(5nak9pB){4W_Y5nzy%#?FQZCN>2e5+me0#Utg&uh#c zsAFk&q;hZhm=A{xpbvhof>9#0cqszD!3VOajE07x=QFhQFy_9j1@r;(<6=Ffg6ycFXL_7FY4oS zKzEr9LYG_Ky;%H>bY;Ltdka%L_`n|z$Z2<_kVyw=3sY)FGylBl^k*sef7He9dPsfM zF31zDvgWzwl|7*_4@w=2y%P0z6(bx|Bfk6c)NeOQl{w?`?1V7ZVfO?WKQonj| z@}F|qL(0#VS?Hsvg~!JO^(<0y&Y7HOsKx;01%W~`!FTtW=aQE8*P>=oJ z#_*rDh)P{T#C;upduOV?Qs>d(W&uP&xAxN6j9X5TahbrUpip;^dKD`B;B4{)VNP0> zTu7Z#69U#>d^F1U{n)&mddGD+(zRz_f|2!@QqevfH(CWM5mw8@@HeZalUIQq5|}a- z;RPD)jC6xST9(Pg;<92!;rG#uGDfFytp0e}k%L1%T^gBW)35kxd&hmPIfnS*qpB!C z$AL@0{vDTz%k~vrq2Adp10Q!;Y}*?{)8oLFU$WrfxffNDH;w^=%bM;^T?@rt?|r#} z>@Nx-PW}4UH;y@fs@?f{jZ@3~M)r`6)tMXZNo!;1(+4a3(ygn5C2vCO_Ub?E_0hII6^FuBYKpq2 z@o9-TzA!H%T)Jv3+~uRT;rimWOSe_3N1oSZ&5i@_zo@j2Vrmh=eFYAJd_dHh z0-xIfijW(#RvXs{G2qyRg^jte`6u3Ha1fw+7$Jca=jMG-%{93QH?7|`zJ`&Af{*sR zq2Xnz2KxaiS;Y`~YGsmL*<#BBL7T*4 zW9r1U7taI-J&yzpRDzd;F(buwhAFr{{5KaPmoza0{Gh+-NfO6biXiXVIF#S{sh2vh z2Aj+Dc3&*y^eHyq0)jhp17qqE)UJf#^N(fcPufoq=c?)5*BWL-O-NoYhx+Y(xSNLK7PNGr{$&Hld8b?Msx-~@f_&G zI+i!kX~2*)-k$sGo!%1R?dg~i48fwYWqjmasLwILf6MfWjiNlxF)*f7m-12e_`K5T zv8O3y^CfkWadtu@BjA2%NsSURPgu#=11i_*<~HSA1d8GXrh~t60X}0c+aM0^NBO+s zhZptjzJ-{s6Oz>tTWVFn}!0tzBohU6ewKtPZnISD9vNP{3zl9EJVh$09gNrIA; ztmGs)ry=JId)=qq`u1VJs;%0W|HG|Y9jd#l@9&&G=XX0&M+;3s%0dbPfhZoTD(iwk zU@Y)B!5~15A#Z0P@B+K*q7^}3df3*00*RaI6L%1ZjOO119+(Qe0D-va9x5y7`UB=c``q$7AmVJ^WfcXD0+;QvnA!`Jx%%~;MH2_pWx4*u-$Qw zWI{^q{z6oon!xE>{};}!33`zmt6Wb#ZapHlQA<$GkZo26sm4RW^J*e2&f<2qb{Hb} zgQJTPL5%m2=CNPn=zjZpX2w|8x91ZqJ}3|b-jRW_v$Ioy^?lU*@Nzqi5(x^{rCr72 z$Ll=T6G5Kd-YG0Z%}OB1DGfZomk=^E<{Kz%kPxfia=m!{`?{wwyuq{{eE~)<%$2E9_oi_w*L|@H;}XqHj;X@sYr=TG86PjIj#k3^+EQY} z)-j2Xk3S6;HeAYriLh+@t}9#-wbFNovc7}%e3$Q6jMwt}fPTnbRT$=6pJdSLVp%G- zqd5>DFbqoT<45{{P?^x<88w>3|0;EtLAm+0SA9woUxPvzegl$0SF( z^l-?5SAGl#iPCG=y;)$Ph$SC=wY2e&MCVmK*GCK51ry%3M$tR>PT4M_YJc*4Ws~2n z#aCRrTV3Mld!a(9+oE;Xdb#KFH#gA70lKo?-Yql`o*M|Qcc0M@=7@{IB!`8<>EeM^ z1Ns3`qqMo^J)iuLayd(FzEpR6g0dX0BwE(X8hlvpdM%2yh;bj=GeGir5P9ep;VAag z#|o$SE|9}&)w`~{mGF9)sl%0H{;AU=(}MllzO0XgkD2GeVAv2lI8Og22^q-^W2~%< zcKZ3iko$@HUT$Re`RCq><2c;qZ@l~0Gkw1p+|~TanutEGpaD*{YP4-v)LUJoUAypi zS38cKw)ecKH}`n0B!>M9gF>!ckT7m9w?E`qOODE4AaA{2LLQ0ZcJ+Un7W3n3H(_*} z*>^Luu)1_wY!5%hT{_ABJLnBkXA~-16*tF2s}`a$gx(>cMN|>xX*UaqH3;J#uSu0E z%zN4#nLnJ2DG3s~Vwc}nIdok}Z=~te0rDq>VR@RqlMy#T-1nCv@fInfp;e zQ@t^lJWZy-YPmE^=>vtw5&1Pev`i*JKck@O=isD zR`Tya{>-%_btkx69M+gf^vS&_3BnIdgT6~%HV58i^8~%G`1dENcFkPp;u7$EkpNO& zZ#uoN35MIxNNr=O)l*=~nQa3hhkr&jv~Ywlo%}NOy8?~GiwaL13NtC+RotfSiebEr zxvi&nJBaXxR+Aw`PY{*rk7T>%v~fP|PHkaD!x`Z-gOaVZWPQV+MXt&#d$W7B2V%99 zLS}c(&+3~P(hLn!0*C1Sn1c&%rrx$9wSDX1tft;%F08_!<4>FBs)%d+V`LipyY-Vl zt+c+f-UaVnNa#N>piMeUTd7klBm54Sl3ma8nMjW2jlVwZVZ6!~JU#imce~c9olrvP zeJH;mucuf-VKt*!nUspm#+LA8T>jtlvh<6+@8rYiv`(jHOVgi4M~YZD0>^+rEj1wN zjtQdj8<_y|@YKedl#c|El7JwGs#qDW5v!3MW{4Jb#IdG*fg&Fh43%XMf$8)!X9d9$ zQ$Y~@`9)f3XcR9NM4tgZ3r9tgAW%G7_p_D$A9edHh&tQWZ9mJ7)2MLRQ|F$eOs%xA zeCLoF9HU+;x+j!tyI+tl7K-4sRR6p!3JOIKB%ceejSUM&$FVU>xmjGS_7nOY zrOWv4Ev~NGU;U76e4NMY)b&o4z+GLNIxnTX3e)dhc!=(7Q3{~J2XBaU%fb%=FYga= zEmMum#PUhgt7hfT{FOJBl{uRs5ceGFS^dORu0!AZdwJVtSyztfAU_v80;CYjfQ5>|iZj9Z6lA zp4e&qx%x_|iu}O&wM2feq^ylw1gpSv%mOBiOG&4#oRHqQo(!r?#FvZMc$^VuMC)#FS%V@R8COgCBkdJQcNST8~`G*TCkUkB<* z^4?cv3E`j&xe2eeeD#Li7}v{H^zrkVtCFej*Gr3?*}uZIwp)MjWz+kQHvRz#8U2}d!}>-hP#2NM*;ame+JeDfGCS~vW1 zu|scoF}IZB+Hf?ksh<3_)@gM0op!@bUK`yo*|-{))^bDAl56C^b=V2TL9?|z^KTvp zwkNeG*O&sozkEAVxu%M`@PXV@vtaQ@CCS|%x^ZErsU*x+33o~PvYOi9S$4R9XGa_P zoL4)eF5fpRI4@?Wyr&NR)<<0>{cGlCo5O}bi=3~bT{BXR4x-IBf)4sGd*y?c$LxAH z{=TrC_?A0Sz3sPE8{*n_oi`>^zhTvCZYYwDh{_}9}2atGNw>R*BR zy)V;MMhFMDdt@C?SL6;kD9T#)#3qN|U=Doj?;{A5)bF(&eB2`uyCKrOEM@X_wj`W0 z&~0si!pe%m@92O{a_3-o;aWoU<;%7=zP+*)yIq1Z_gu{ytskz8Rm2o%OKeSeySTaa zUozzVjyBrb+Vlg%Ra2;Z?_%(}jlpK#pXe}& zs;+8RwJ`bdO92L54!_iJpHFS*tUjoLe;_PVfk9D2@isHf-0p*;)rSfttB$WZ2mJE( zc^4F3!0u3zApJ#XiRW#RZZ#u{)b~Mui0y7T(uOd}{}z*!JQ;T?-5js_-WX)zbJFtX zz1T`GPTt)X?ROp)7vQ`fMZX`%E+ZxRSMshteHQMr*lyPJGi8qp#-Tk^cCj(~8`GW@S}UQlg}k#El-`sz3H#>2|vB?XuC4AolB5 z()9H1%#~MORn_yJ@nvoTit|kAA&iapFADjq((AyjINm@h5C6`Nj*gw3rbt>5;io=} zX->)A88X#{g@x0DS=ZeX7yI>D6_!!YApL$erNr8Z{UFv z?h4iZc>Ddn^}12}smf}hp0${r+2oMNZ(8jQc^@7Aepulv5VY$7;v48Dsv`?gQ;{Q2Ma1i1l_|tz{dZ4JHj6l8i3(Z99{$wpt*(^818{xW7<(R`i4Gr$4KlXNV;{s1tlp158>_<(V5!h1ls^!}pLrQUGbK z-oFMPWr^9anzfJxyFo|@?JX^z!s3mNrMz7{e0(e|vgQ^$!z89tB|TJsaL9YArOy@J zCnj!hX$%P9mhsM%_1f538$6emmj3d6t8T(&-T(Sr#JqY-1KmECB#4%9{`=LkHOdXW zB;|0J(PZ3~sW*F751Dw>jTx8NE;FcjBM(C%LQ#rfno)gniVO#5x_8C+g`vWPtH3gW zyJsETGk!bG=Nv&7=w8|Vj-CveOusFjvZjj;Zf)7q#a^8M-bBto?c!VY#s5d$!UWM&v9jan^0d%DDVE9PyNxu9**GHJEw%yUKDQ1}J%~>Ao1z z@Ddq`8v2E!@okMtA6M?_(GV>XA$b2c{BNj4k|lcZ;N29`LJFAc25Iuh$aF(PbF_#{ z;EK$Omq#TzIXSz`(OJ4wjG*C{A0B&UE>m@%OB;;;RJ1HiZ3kBS2rfU#*@^VrP0{7d zN2|TnpcAkbQF0hm>6^)|`_em;peBp4KD~qWBczFVA(Xn6pZe^I%ipeb4ug*k zyJ_ueIQ%wZ+Bw#Z8~hhzWwwkO{A*LKHRT0pV^RY}kWSJGt%hFpgQCz$UPLLOIzMer zRP9#XaC_YK_Mo=>`CltEN09$DnTYJ<(2yWQstAqwd7=Kt&K|ct@ouxf><~DA^1s;7 zimqA&!mFrsdxjIUx*sR|GRS=9`svGu6ZqlJden&8ER$PlKiT>3`D4OSl+d?i@ipb@ z=}mR}Js(^e1G?K{$V$I&mB+A3d@-(a94USxb)v}#$6^Wo^@N$-Qi-lG9)0Q_lX_>$ zf4_VF!qCLTMBuYLWjqfQh|af7l~C16VLajmy6iO>1cR76pnZRfg*I6z|c>(4?M`hFa+ui&@1aUpr4&Ab(YZ>24Dlzov`4`0iM z$q^T1=0PEgg^?FMpSEP#3#+Rt|A_Den<)O&W-I(fkkU7aKu|t*2X{G8sA}qZwCo`g zl4x|fS)m=+`fY6Z)mW6gy@V-UZ)+ayw`u$WG$-cA@ zQjoHiD;CGD4qMblP=VhD{TlXHHZM<_Ac%Wjm15V9@|!YJJa8=}9GyuhCqG8!x$P^{ z4ZLoDto?z*<1~&66N>r>wG>fA3%l6izdT&Hdd>Q-{%bG@s*BWFWQi}bS5*6;|LWjw z&auho+nqV)9=iaPY)TBp;7{QWi2}~(v{Q+W9)DHI-sdF2M-&ts%9JEUV!3Q2_ zEKC=6@g07A-!SUqx>SimlM-TOo!EI67Yitmv%$|;SM ztsqgEOp}+H<%8LA6O3WNLOntdj zsC7eOQ!SG?GwKKsNgG`8F9}F5T z1t5bp>QCI@apOY$&>Ope_L(II?~2dS31*S^z9N}dbUbB*z}>=7c)fITLe*nWOUq~< zd_Hb+erBsW!NlV{zW!%70I@ep0($Ph*OIEI>yv)XWesd5jyQ9}eZH;|C$7wP^#m2> z-MlYKmo*fSQCv_rBo_M0MNd%TrC(k0T%>}sE?do#gBm$&z~A%sin|@es`Umant00s z&Wp^=zz_Z%X~$gXkXpH9ctwg@xq;~Wj#bp16ibF`iQoh7IQQ&f>Mi zD86mQYO`r&px6yjcvZdjxE21xL?S$B1etaV)X7dkwLB!Y_$YZ}T!MOj15;qb3X4$4 zoY<>)BXL-i(<6XjEq95e3R~Yz_K9X?rtWwWg0F!L2(t8QDAW&+dY5Eq8a>7sEonSx zXX*~4?a&M9G6T077shBzSU)^H%Fz|-9OR3e+i!R4Cx1#qnjX&bZQ(^2!SF+fuKAFd zoAcsFg+fCVvl*G~bCQL{+dTZRMHKKsr>3e$+VhzT4V|QR^2z?E%)!&^ghlu5;bp2` z_Bg>%O%#xujvirpU+L=eZURLb$l7!)_b<{88nYT8iD;w_0+-(tmm~ro138tgl~fqg zLI@1)7$2Htf==>bLA5w5u25_haLzdCg;>Gea3IKHU~D8DGD3sG7Hd+(Dl`Q^VDaZt zyo&!B>_2<F}umyDH1k{oQ?u2roBFYKD4g+xgJQ(5* zcl!Xq)hjTP5@J9Lz*UQ?N1^FC0N06A9%-17CV+iuIZ0x0HGqAWm6TP4g*bqH{J|_6 zAf^HAXPE!*gP|NpU)jV&I(2iA7zzwK<84vuAk7U(we{qpS($L_CqA%ft+hN&`e&lO zamz*!Sm?$=a9yLiXFcWH&}g_@60kZ)+ek8qC!j%#LT=1) zL7(zqLBoHXJJ@Y6Ig>WMD!I%q=kRk0YEMtU77bEIfbvD#y8cE;zRu(Dls{n4?9_RnHec$w?}$?8`{YY&^0pjd^Ecm@*fNyW3iaAVQ3^qua z{&nzu=;#G+GVi^^SfSew#0PHtf>UzX?3xq_d1*D*ury2)?<8JZ`n(YLpL|NBNE<`v zC;mg4m_{Ofa>MDxJIV;{YUeQ;Um5Tij9#aSPD9=>5VXbKj8dR$6Q`PfcVc0C0p)>#?U z&*;)wQ+o@=tNc&ElJYejV@b8alzkoR;I3B!&w*t70{{lx8cYfx$P>T?m1n7a0DwXbY%z@X0pN%jpz{H=(qvTjw1BvI zgFU6_w1maipxW+egc1PbC^zlIM%;n&bE6#?0H=clyL1}=%%c?rWK|ParRixCD=aYz z_%~7HL3<0R!-B?=sD1>a3ISV{uO`u~*dz=9?Z4SA#WGP~YTBZ(6d95cR+0?5Lm15x;!g>z8gOV`SJ`C$k8QAyjCY;Oa32CeWK)vtOFu^^qWXDn$vZa1e);aMKO z11!*KUPKRZruHbdBlMLg97~m7%zxXLY2^Qaums#7( zGb0}Fh$#|`i*Xhh;EgRH{ENTh1JRSTi-o8+iD~o0bTTVV*VU8dUq)pMMyJ2=zJ4 zPRD3KV_n25ZNBk7Ox#=@UfU3#P5KGCIrLlj%124oa9>X6q!MJvC{-Me7GoR(>>y3YeR$ zSej9uK1zK5KnM@hF_iGpp2{ZeNo=E!&@8#Lg2g54k*h#Al$W}CmcFjS!+ZP_$L*tihUc=|IfSu z030TabQlPLXdCGNav>+CUx5t9i}xgw*9(@JoO=5!X}G1E`zL%y^CTLQXAvsNr2GuR zZ;;;}|nx>WR;f%xvX zgKn_+kdU}|H7!&`v=M;;RpW>%0V_Web%0^IEH2dG<@M&%c`I=N9ctidfNUJwHZI7I(wPW6K4hD96^62-$77`h<| zE)>C2`?O8bP$**AIHN-iwIFB5SUPYV&DhRk1oTt-!n;pM3xMf5qVCQ@p$kkTX_SI7x)`TUKi(yR)+G2koVny=D>^&Js0`y z-uZL*M#X#>;A`%>4@5*1^b=M$^r0TWiZ{Um2tD1u097(m1BCp*;veTl02)9=>?Z`c z6%ql0BvKoty#_&Z0Iksf<|~jM5(PNV2AXUOToL*AxArdgEkLil0qBo8IR6RYMbbbJ zx^CD%SHcZ6|JNZ=hsIw!)(yn)AN~y?N*gb+H46kfDvJqnO~uN`7i(GWJ_uZz0Xt#K>m2`#p7U zcRCUM%zOL!ywCpeZhz|Dy62p#I(6#QsZ*zJ*Dc?}k{M%0BvobXAf9wp@cTF4P69n) z%8L`&{xQG0`JiFJuWnu<_&O~e!N9s;(|SvDQ+s$$3J^YQ%tb2n_O;qbZqz24U>P?^yj zI_-=tFr>0|SKcm-;XYs!4C4%GtlJ96I1TEi4x{7Uh4gM<-MCXqK|Yr7%DO9Y1Ksjt zL`4@0X3!Hi{)|5e$XGFBPI}H^>?jF#^i|H-vLuuNFFB)e-|$X+?*0b+OwrpV0^jv* z+^AprGqH$;F=1w~rAcgJ>{E1x_Z_@wG8m?;V60}QM#f5s&W5BvhpD)Z9IahgyM$nm zQeV+`MslTR!ewFXqnW|ZU^8G`CXfxMqi4cZ!PxwnL9agm!dt06$f{d^CXkVC&QE;* z@8rXqbB#L zmWU(6sb*aSOsHiV{9PF8uiFWC5K7cYF3hNXG|Ux zs*?@Oqnw$+*p6sJgE&=QXj1OJiKCWr)CnCGG7;*nYk}%tOm1ksTDd-rvBO5TR6ZL%o9XFKc?^sjpkm6URH}Zbn!bBKq$Agv4rZby zsj@<#X8a1D6p z`Bd4^W>7Q2ttJ4>99hbdl#e6Ltb1!Y3+2W1s?Skw(9}v!m7!y)(54QI7pW2k)QHx( z>SyZwP6EG&&CKQY$eVLBqaZC+6 z*5ta?tjAC_3c6KlCyE!Bd`?lXsOoCWmK7vHt{sBg14Z^k?B{eXmP2OQu_mnK_ObN- z3UjjYcGkA6L8>uIH72R<;fVcV$x*6N^jG9CXhx*&oK$1!t4RI;g4Hxp{*9)KJENN? zcyMXe?<1BR*{d6A0QJ6MzGqVZI!qqr9_rueo$qZndz9BwAaUfkOwp&NF203EIaM-% z9r^W`_~c&dj;8Ax2UK^L$s=tUQhxmxU1rzGb>>kbJ2M;VC$BQgRWLF&1{j$W={x=& z>RMYX&POT;pSf1N`AXH)a5Pt{S~J|cH?`i$=gyo5wMRLLMx@T{0b}>NGDh@OYjOup zdiw4xH7NUm(?MUUAabWAe2lcvq(5qR9@{#>jVs)r0V6^JvqsO*#B;kbt71dcxhCRx zUmk2rYq;=4_nKse4#-%M;3#7lSiq5DdGctjk=-Kqx=DjOTQy03*8(rhFf#kYN;{1 zQ5#m$((PwynR00(g?h)h<<8R1w4YPm1L(*EXJjV>M@}_lQf{HTkM2CS+3K-I`Z%Et3`H~dfH44=)~m? z-INX6A^+PdUHf(IsxV<7Fozr0V0p5emA5Wqs9<)tj3LQNTeAn2$t%p;2a@4}iL=53 zM)5YfT^An65z}6Mi9|ZxO@eH~Y#f-vvy-6_1DQPgC(4fDS*|C8YRHUAl_${5b`|RZ z6DL4lPH4(vRR_}K4Qsj=CL3twTCs}k4vd#kt~Bk8ipBDt-B>QA#kumHJv=i_-m{ly zrptRC=9zqXPY=)7c6I*%nV!lLZpU5jS7?3kP*XcFPhdzgcAZv!2(EX%DW8xB)vO+} zH`FW)>~S@#o6n}+#zRY<0A)Y!*vtLW#xIDh$3 zMUM%{?;;U#2+4vp4k=$l8MMcCK$3J!!PjD5(lAM1E%V}4F$iw{bWy|JY_pxY&eDaRBw_m1-w_@u= zO!16X$z+YbNV@s-z&Q9H8F%!zN=epJ^5NB3s*cCpkP1^+BwOWtB`OOfinZ0NBMMncMkC-!M8hNd&A z@?xXpgaR3%KzK1pPE)wa#KKKR)_a^5az7>$z-xaITi{?zGk z&6GC*x&$ZCAf~yMt)HuE138hPkq?o6N;Mheeo8et)?@vQO6V(KjhU}q+}D%OTifQ!h7YuU$2-cEi*~WjC%29InpvS`5^EnX$9^jZo0Rcm^c%TL^u2i zav;>KXP~9wRx?rt8yKT@nANPONRLsf0$mP$(%5_4-Y$BB7Kf|aQWaRgzNx*%lHZk= zU$P8FEavf6`r*G@YJ(jKFi(?Dw6q3-me$S|OI-+Cm0^097bt+gQXwG{TXKcShi(n| z{hRbwv8QYeSn|4P30}O|MYiE3BX()R%n9UmoL4+hn(z@bbv88ipxwZtXP!sP@CpW< zk*(A4%!KLAfF)5k;0_7yM~~6hOyMr7LYagL8X68hLdI!}T(d@Yo2A9s^1=-HWNang zeuZpLXsR_zi<`c7%IAbeRNz*2-lF^1WpW4>%Ou>#!R-Kpu^mw{4<()@G23%?M&-I8 zF-bBlCc;NJ1K;I@8Tyi!&5rB)iV;#KNmX`kYKPbZVzWOkN_}4vAEK|L`>JF~qPmvE z1oux6oF*$cjG^gjXqp#}LGM4TlLjvbb9T|R(Gk|9CZ5DNr*1ogX>>8M+iU=~U1Y`oEC z4FOGGb$I&K<)zCou9ce9G`)=N+aS`B!qlZmG8~Dxy7WjNX_}Tanqm_Cmtob*_PEqg zwi?PDseL5FxxJxm;e(8r4EI`wWf&Zm&Q&zoke}qk z+#aDOPIVGga5i#eM@~b7+L57rLIAi1p)_r3-Nmh18|}im4TFK5ATP{;C6~I${U__? z1ad=$M|m1$X~`v1rDpvC5|dNg3|Nda4$|fYn;NVM{RXm(+!Dw3M!gsg<>N%-Yd9A- zzM5U*8~8jIhRGfb!dg`YIyPa?ZgGi0dfb-hy3OMEwyzU~vOJ$f7|3h9`zuS~b)vt%5^x68K0pOKO$um(yQ}_8W64WZ-mZ;!b~| z?lR4-H{?M$j#wzR3|)9y{w4X1KP6MCW*sLn55kAM?8FU_(2y_*oOzUeH~ok5axZsWu}+d{()1;_jmhWZ8Z#dDKNC@p{ey56(F<58XoY5@LOGajPQiK&gz3Re zpLTFIul-L|g{8=)HCoKYI$%_mqE)x@@1H&ntgwa05$v-@8+shR9)}<~mNY~hEf6e47GSD+kLw{K z%lu6ubf?)egRgOg%aFn?ygZS{>d4lL@NEowLTgmSvBcSC^iaq^xCM+v93@VdP{VFuP(&}^ya`7D zcPkIwpqrhE!r3^-#{g_-;8B3y7|-?IP8d`!Mm}L!0w7^d&}<^_jh0wpIyHD+`5rW1 z-H?fJR@Wrb#L1{WwjylE6!rk98MDP2f*kVR;}k`SAcj@1I!%eP^TL<$tzYkL@ik!p znuVsIB`+W17vW%#MQ~wqe*=M=O9fR|=R(i2H4Z=jQsxj?bWa2XentlV= z*HP1e(ztzGLld{epY>FIJ^D@23QBE8CH)Kdlv)&J@#1c;%EI13=8l90-Jm5qX^7ZW zXIrxJ$4f8`b%-N1(mER6GKjr!_^3KyOg~^(kD+pmaragq6DP@zB`~>Z&`0w4x?2S# z9|pDY%o}h}V`D{2pTi&62z?iW0spY;M?ec<6Gu#2FJqn>>?PC~Q#X7z1DMK z42tmxEfBe~12c~d**PIMo76Dv`j|@G1f!yo2X;{nofE2o)udg-DY7vIkI}jjn@Q-) z65o2SWl>1%)bxNW!>F648^eU7MKFqd_*E;&rI86~NcOYTEy<49M}`NHOxd2P_c2@R zVs?jv_L8Enjjz&j{cIj)hG3>FAX-kgSO-{kg``5E- ztnw06SMMTu=PqIFWa`?i>!@ql=vof%nuePLoYtjv8lG>xSB)Ca>Lv9S&N2z#X9={^ zye8mi))VnAw>4T)oon{M?#SnzVw#W(9tTo{X{2sh;4=LH#Ys=gj!dUV_#HTLDc^w~ zqD}Bg;L;;piSyU5VGuk)_&i1v&@qSE?5B7I60tUF#SgH@%47*&QW%zSyR(8 z-QEPf9sZ_fZ|t=z(C+uG_le}Nbp={mV=uZXLcCV99~6of))4m<;2y$t!!y|pK3xjt zhR*%+@RoC?(8TRqxQPl)=}#q#HE=z^NyA`Me`+#;lcaJPW~$CCFU`Ori2!bCuWmI=?jHMC7!Qwb zHIiX9Nbz}(mY_#)2p;&10**q=SJTX60h26XxTP##Wcc)~Dw=TN9=bTOl0Ta53lZsvCmo@BYxx zW8xehatgPDL}Tkwkimp#wt0AlQuFmoT$<2gd=cRPhA)rC*OQ#D-%ix|x>e)r&U*nTZ`CkV<(^X@2iW3U(%7P1%%W9-}j6@oT8g=K(q*+>SZ z8(7#AzG%m-D77_gtn`+K0bj!kIrlK6MsCG~3Swj)7$FmS7%h5~BN?&TpQ#oLzl(99 z3~6v0p?KzAf2y(KxS3d)J%W{1z%*sKcLXcPxiekq z4|5rqxcbX_J$1gpIoNA|nxr?w{sc z3QWurPQ@8Lnob)4YqYV~Lp%09hhR`%hK~$k|C#sD0|J^Id@nfzn~D%@6qI7pEu<8) zb*wm1C8wMVLnzG)%nn|342WVfU$XjX%P!xapOhX zzN_FCRMn4&?T}j(D@Ti2Z0d8OtCXjOoNp_Lz}|(xc!D4_w`3*U5+32OUTU+s<#(je z=~gN9R%^fgH#R*&hLFu~65=(d35rD2Q2=h17*WP}Oy>?6nuFLEx*z(qp? zD4&Fh1^?)!@JC>r%9~?#gD<}(28HfCe7mxn99oSgDUWD=mO!&mVDJJ3hRteQk}?^K zs!n$@lqs6lyG0j&1$>f2M`yyz&uPi62It ze>WZvC0qgYz#8pOtw8undO*bHTY8{_-~G5#(29a#CQvqllCqJ7euehvv?4~fn}{mI zwQFUrsV8Tm%C?vAGreFk!n*2q%7YA%TB`p{18SgiDXlr>u0*~XhWX-K=4^<^7bWT7 z^DOL|2l0i1;*_w#=ZZ<{gbx`ArhUkO?X3~9x7oZ+92#@Zr0qsHevyHoYSM4wDv6vJ5I`nzkkQ!HwL8#>HM2W(l`k!oDr*#&dLchhGR@l z~5q^~&Rq4x?fi z+JFIb{Hqvz-9wm@!0g)1tGo2h4adSCrb#_?$lr?(c+M+-!Iu-l0^++H!Aze3%lGv(FNLsW(IP#1`$hr0QF2k!k3Y{ng(*JcfAIj@M} z3!V7v>wG&t`|4{K#n15aIuW2~7unMGd)fmV+bxMW>^?dXx4bG2g<3597=zZ7?PrY; z76lDSldcq%W+Yyz&+|(IX$8TAee&BoE=$vbQ;d?tzMRpFMk6W`N5qjQn zUjF5-%+#KY?vJ)rB)wZ3`V5;{d1Cj!Z|lj9JkKfmD)1!?<(z80NwvMa2UQ7s@4&5s z2L5$O9S-}7fo$O^01-bqLO6UH$du=RLsXbgQaZ~w;e_8P?RaB1M1}@)w+YLN46G-s z0rwv-$4!|Il|ulOzT7lI=|k6$8Gvgu0JtN3#kl#iMye;QF!A;p(NZH$stL^yI#3iu zpMpujkNm*3fuWGjo8|Lg*0jNJ8)CK>1$E=65RIyZKCfnVv{@y$Ahmi zr(+b3_OP*fs{CHWv^m7>fweY6&mY1vi8UoSgVvOHF=_bL^j5y=!Ta6|yAJdN9Q~4x zmQRGt%HcD>^q)C__FUHYTrMnYQ_gbYM>z3AiNxR4h#v!S!dhui{)6Cy!U0}lWxRxV zqRpU$wc1W`T*WZ%UtNkosMEqnK)IAW9ffu9gfbl!4$OI#RtAsq2D}4dC5`C`Jjy{J+KhO83DeJ``~9w(zY0(_!%57PTT+ z*S-!XH3*Z#*1OXWB3p{d5r$jPtw?f8OBi9s2HFX9$5 zzl~`?wsBybTbNESd`|oj9tGwLXXbbUH}yDwZHlq;*Kjjk=LRQbZsmuNLiax2KFx&t zI0rvYJUcur6(Y2asZx& zgJwN8d;EU9W93WV@dU6tqMOD^;|~IK<`7<0j+L_rpr+7Wqwl$YAC=nqIB3!jq|i$^ z+8rJ=Z*-}-I~Nu}K7RoU!?pX580vC-gQeVooD0tjXm?;bL9W*!%^1>w$plH$A-^Yx zNj~9GKL3NNhV!Ups_iyNW}u*~8+B`3Zq6;EkL|bJn0}y#!IoA2n0V>utfC00sC(*y z+>G>{X_()uSJ3xx4ZT0hH?Yy^0$`M0MtU9TL;XDlzbmjQwvS9W{X;CTOFQ;+JDtM) z)O8c|1e5lDw2#;j_q|RiU3AEELTNzHcnlTmg=NMJ%fcnDC6!B-y5_{ok`3u5Boh){U9smZ#vVd?5@|ouaisT= zhLCbfU{Ua#gQpY8kJN+ocS!q?`j9?CI*YU%bn}r4k#0gVAzdtHY#MOyL?P7FbroHh%gqWa8mdE0RNAa`hSp+0NVrD!+>4w`kyqPD-d7dAIjle%^3*zDP*^#X&tKr+!6GSfW2#|4YcAL z#16k#^v36rIq7aM1&w?Z@bdD=B728ZHu?S4LEi?17_S*YgE$%B2o7n5 z*lo$qK-F*|X2m;?qAtY$ww!^D-XMK4xdvq@X6*gMD{Yg?6ggPCaE%Dw>i{Z7X z9GcM1(qaV5fhRdpn-cPiL*9^=0;{#6cRjNJ5|eHm$XWqKfLp6kW6T-w(KjiK{jcPD z(O=ntkYCHvc3(3rHCn+17JMldgV=MJmhwFUgE9JCO!=)!1Q8Rt{vd$Y^ z63{ExtNz=R&JMI8wz7>Wt2cDE>o2Tb40PTTZ1>OfcEMH~pcA@9b~M3^>mR9-1~uOs z@&&yK&_(R-R-HCJ+zSBHDdVsTSbfN%Dip*piM7Fd{aEWN7tCz&`w=s$@nIy^`8pw* zI$yi5Mbjo2(W(WNF4s&~1LBYWuP+0)kFbT^BU>h~6#v1x3VaM~TMck)<}lU*`!@P& zNyg&I+=wEt#@){9k*j5k*h0M5;_g76u9uCUUZGK-e_vv?#ec!HC)sUms+kc#vagBP zVF0iRU?PuCx3hJq&4(zp7d5xDRu%y9qnsw6*}(m5BpX%>^aFA2n^3k2H0xOhV%zO( z6S!%@8FK^+Ia*{^|>W=Y3 z(ezHl(kX`C0bYHehMMCN(OxH3DXmotTL9cz&@~}#0)83(Av#=-o7di%P`-%GWCcit z_`@afG?VkSmW4p$N9`8Wwh4bkwdlRkIA*K_E17}INaS_4qeXv0?=`;G>3q!ukDHKa z@Mogj0+b<~pG&U;IO^Ft?CnI*`~PJA!YDQSF&iPwtm0Ys z&ia>GpUs+7@~x7(lC>pWB~O;TIj3lD`P`**Tju)99xZ#N>{n&KEgM^YWBISk)8}Q+ zn>(+5Uc&+C@>e=3YQmt zr|{Ln(}j;&`>cPoGTW`TIksiCF5CUK@7a27pV&ULEh}m++E(cH z6#HjAI4iy6?UK(+j?X?f`yc1@&Z(Q*JooLnf1cY|DwgJyZ75UAYRgZRe_rmJw|gGc zl98+o`M2ip0Vk6SW)?hA@biMTg%1`!QFx&6KI@~_7p#Y^ry;o{+jB*46unn;xyV#} zU2#$IoZ_0|HO2Q7pD!La>)W%I&)PEU*;%j6Iy38*lKV@(Q*xr@_a)LX`mAXq;ly;PEEPcH6C#C;fdZzUA(xKAivaw}1l+7wLmQO8rmfv6g zX!*~}e^Np_6u8-jQ;<>{t5~F7uSt#K>m2`#p7U zcRCUM%zOL!ywCpeZhz|Dy62p#I(6#QsZ*zJ*Dc?}k{M%0BvobXAf9wp@cTF4P69n) z%8L`&{xQG0`JiFJuWnu<_&O~e!N9s;(|SvDQ+s$$3J^YQ%tb2n_O;qbZqz24U>P?^yj zI_-=tFr>0|SKcm-;XYs!4C4%GtlJ96I1TEi4x{7Uh4gM<-MCXqK|Yr7%DO9Y1Ksjt zL`4@0X3!Hi{)|5e$XGFBPI}H^>?jF#^i|H-vLuuNFFB)e-|$X+?*0b+OwrpV0^jv* z+^AprGqH$;F=1w~rAcgJ>{E1x_Z_@wG8m?;V60}QM#f5s&W5BvhpD)Z9IahgyM$nm zQeV+`MslTR!ewFXqnW|ZU^8G`CXfxMqi4cZ!PxwnL9agm!dt06$f{d^CXkVC&QE;* z@8rXqbB#L zmWU(6sb*aSOsHiV{9PF8uiFWC5K7cYF3hNXG|Ux zs*?@Oqnw$+*p6sJgE&=QXj1OJiKCWr)CnCGG7;*nYk}%tOm1ksTDd-rvBO5TR6ZL%o9XFKc?^sjpkm6URH}Zbn!bBKq$Agv4rZby zsj@<#X8a1D6p z`Bd4^W>7Q2ttJ4>99hbdl#e6Ltb1!Y3+2W1s?Skw(9}v!m7!y)(54QI7pW2k)QHx( z>SyZwP6EG&&CKQY$eVLBqaZC+6 z*5ta?tjAC_3c6KlCyE!Bd`?lXsOoCWmK7vHt{sBg14Z^k?B{eXmP2OQu_mnK_ObN- z3UjjYcGkA6L8>uIH72R<;fVcV$x*6N^jG9CXhx*&oK$1!t4RI;g4Hxp{*9)KJENN? zcyMXe?<1BR*{d6A0QJ6MzGqVZI!qqr9_rueo$qZndz9BwAaUfkOwp&NF203EIaM-% z9r^W`_~c&dj;8Ax2UK^L$s=tUQhxmxU1rzGb>>kbJ2M;VC$BQgRWLF&1{j$W={x=& z>RMYX&POT;pSf1N`AXH)a5Pt{S~J|cH?`i$=gyo5wMRLLMx@T{0b}>NGDh@OYjOup zdiw4xH7NUm(?MUUAabWAe2lcvq(5qR9@{#>jVs)r0V6^JvqsO*#B;kbt71dcxhCRx zUmk2rYq;=4_nKse4#-%M;3#7lSiq5DdGctjk=-Kqx=DjOTQy03*8(rhFf#kYN;{1 zQ5#m$((PwynR00(g?h)h<<8R1w4YPm1L(*EXJjV>M@}_lQf{HTkM2CS+3K-I`Z%Et3`H~dfH44=)~m? z-INX6A^+PdUHf(IsxV<7Fozr0V0p5emA5Wqs9<)tj3LQNTeAn2$t%p;2a@4}iL=53 zM)5YfT^An65z}6Mi9|ZxO@eH~Y#f-vvy-6_1DQPgC(4fDS*|C8YRHUAl_${5b`|RZ z6DL4lPH4(vRR_}K4Qsj=CL3twTCs}k4vd#kt~Bk8ipBDt-B>QA#kumHJv=i_-m{ly zrptRC=9zqXPY=)7c6I*%nV!lLZpU5jS7?3kP*XcFPhdzgcAZv!2(EX%DW8xB)vO+} zH`FW)>~S@#o6n}+#zRY<0A)Y!*vtLW#xIDh$3 zMUM%{?;;U#2+4vp4k=$l8MMcCK$3J!!PjD5(lAM1E%V}4F$iw{bWy|JY_pxY&eDaRBw_m1-w_@u= zO!16X$z+YbNV@s-z&Q9H8F%!zN=epJ^5NB3s*cCpkP1^+BwOWtB`OOfinZ0NBMMncMkC-!M8hNd&A z@?xXpgaR3%KzK1pPE)wa#KKKR)_a^5az7>$z-xaITi{?zGk z&6GC*x&$ZCAf~yMt)HuE138hPkq?o6N;Mheeo8et)?@vQO6V(KjhU}q+}D%OTifQ!h7YuU$2-cEi*~WjC%29InpvS`5^EnX$9^jZo0Rcm^c%TL^u2i zav;>KXP~9wRx?rt8yKT@nANPONRLsf0$mP$(%5_4-Y$BB7Kf|aQWaRgzNx*%lHZk= zU$P8FEavf6`r*G@YJ(jKFi(?Dw6q3-me$S|OI-+Cm0^097bt+gQXwG{TXKcShi(n| z{hRbwv8QYeSn|4P30}O|MYiE3BX()R%n9UmoL4+hn(z@bbv88ipxwZtXP!sP@CpW< zk*(A4%!KLAfF)5k;0_7yM~~6hOyMr7LYagL8X68hLdI!}T(d@Yo2A9s^1=-HWNang zeuZpLXsR_zi<`c7%IAbeRNz*2-lF^1WpW4>%Ou>#!R-Kpu^mw{4<()@G23%?M&-I8 zF-bBlCc;NJ1K;I@8Tyi!&5rB)iV;#KNmX`kYKPbZVzWOkN_}4vAEK|L`>JF~qPmvE z1oux6oF*$cjG^gjXqp#}LGM4TlLjvbb9T|R(Gk|9CZ5DNr*1ogX>>8M+iU=~U1Y`oEC z4FOGGb$I&K<)zCou9ce9G`)=N+aS`B!qlZmG8~Dxy7WjNX_}Tanqm_Cmtob*_PEqg zwi?PDseL5FxxJxm;e(8r4EI`wWf&Zm&Q&zoke}qk z+#aDOPIVGga5i#eM@~b7+L57rLIAi1p)_r3-Nmh18|}im4TFK5ATP{;C6~I${U__? z1ad=$M|m1$X~`v1rDpvC5|dNg3|Nda4$|fYn;NVM{RXm(+!Dw3M!gsg<>N%-Yd9A- zzM5U*8~8jIhRGfb!dg`YIyPa?ZgGi0dfb-hy3OMEwyzU~vOJ$f7|3h9`zuS~b)vt%5^x68K0pOKO$um(yQ}_8W64WZ-mZ;!b~| z?lR4-H{?M$j#wzR3|)9y{w4X1KP6MCW*sLn55kAM?8FU_(2y_*oOzUeH~ok5axZsWu}+d{()1;_jmhWZ8Z#dDKNC@p{ey56(F<58XoY5@LOGajPQiK&gz3Re zpLTFIul-L|g{8=)HCoKYI$%_mqE)x@@1H&ntgwa05$v-@8+shR9)}<~mNY~hEf6e47GSD+kLw{K z%lu6ubf?)egRgOg%aFn?ygZS{>d4lL@NEowLTgmSvBcSC^iaq^xCM+v93@VdP{VFuP(&}^ya`7D zcPkIwpqrhE!r3^-#{g_-;8B3y7|-?IP8d`!Mm}L!0w7^d&}<^_jh0wpIyHD+`5rW1 z-H?fJR@Wrb#L1{WwjylE6!rk98MDP2f*kVR;}k`SAcj@1I!%eP^TL<$tzYkL@ik!p znuVsIB`+W17vW%#MQ~wqe*=M=O9fR|=R(i2H4Z=jQsxj?bWa2XentlV= z*HP1e(ztzGLld{epY>FIJ^D@23QBE8CH)Kdlv)&J@#1c;%EI13=8l90-Jm5qX^7ZW zXIrxJ$4f8`b%-N1(mER6GKjr!_^3KyOg~^(kD+pmaragq6DP@zB`~>Z&`0w4x?2S# z9|pDY%o}h}V`D{2pTi&62z?iW0spY;M?ec<6Gu#2FJqn>>?PC~Q#X7z1DMK z42tmxEfBe~12c~d**PIMo76Dv`j|@G1f!yo2X;{nofE2o)udg-DY7vIkI}jjn@Q-) z65o2SWl>1%)bxNW!>F648^eU7MKFqd_*E;&rI86~NcOYTEy<49M}`NHOxd2P_c2@R zVs?jv_L8Enjjz&j{cIj)hG3>FAX-kgSO-{kg``5E- ztnw06SMMTu=PqIFWa`?i>!@ql=vof%nuePLoYtjv8lG>xSB)Ca>Lv9S&N2z#X9={^ zye8mi))VnAw>4T)oon{M?#SnzVw#W(9tTo{X{2sh;4=LH#Ys=gj!dUV_#HTLDc^w~ zqD}Bg;L;;piSyU5VGuk)_&i1v&@qSE?5B7I60tUF#SgH@%47*&QW%zSyR(8 z-QEPf9sZ_fZ|t=z(C+uG_le}Nbp={mV=uZXLcCV99~6of))4m<;2y$t!!y|pK3xjt zhR*%+@RoC?(8TRqxQPl)=}#q#HE=z^NyA`Me`+#;lcaJPW~$CCFU`Ori2!bCuWmI=?jHMC7!Qwb zHIiX9Nbz}(mY_#)2p;&10**q=SJTX60h26XxTP##Wcc)~Dw=TN9=bTOl0Ta53lZsvCmo@BYxx zW8xehatgPDL}Tkwkimp#wt0AlQuFmoT$<2gd=cRPhA)rC*OQ#D-%ix|x>e)r&U*nTZ`CkV<(^X@2iW3U(%7P1%%W9-}j6@oT8g=K(q*+>SZ z8(7#AzG%m-D77_gtn`+K0bj!kIrlK6MsCG~3Swj)7$FmS7%h5~BN?&TpQ#oLzl(99 z3~6v0p?KzAf2y(KxS3d)J%W{1z%*sKcLXcPxiekq z4|5rqxcbX_J$1gpIoNA|nxr?w{sc z3QWurPQ@8Lnob)4YqYV~Lp%09hhR`%hK~$k|C#sD0|J^Id@nfzn~D%@6qI7pEu<8) zb*wm1C8wMVLnzG)%nn|342WVfU$XjX%P!xapOhX zzN_FCRMn4&?T}j(D@Ti2Z0d8OtCXjOoNp_Lz}|(xc!D4_w`3*U5+32OUTU+s<#(je z=~gN9R%^fgH#R*&hLFu~65=(d35rD2Q2=h17*WP}Oy>?6nuFLEx*z(qp? zD4&Fh1^?)!@JC>r%9~?#gD<}(28HfCe7mxn99oSgDUWD=mO!&mVDJJ3hRteQk}?^K zs!n$@lqs6lyG0j&1$>f2M`yyz&uPi62It ze>WZvC0qgYz#8pOtw8undO*bHTY8{_-~G5#(29a#CQvqllCqJ7euehvv?4~fn}{mI zwQFUrsV8Tm%C?vAGreFk!n*2q%7YA%TB`p{18SgiDXlr>u0*~XhWX-K=4^<^7bWT7 z^DOL|2l0i1;*_w#=ZZ<{gbx`ArhUkO?X3~9x7oZ+92#@Zr0qsHevyHoYSM4wDv6vJ5I`nzkkQ!HwL8#>HM2W(l`k!oDr*#&dLchhGR@l z~5q^~&Rq4x?fi z+JFIb{Hqvz-9wm@!0g)1tGo2h4adSCrb#_?$lr?(c+M+-!Iu-l0^++H!Aze3%lGv(FNLsW(IP#1`$hr0QF2k!k3Y{ng(*JcfAIj@M} z3!V7v>wG&t`|4{K#n15aIuW2~7unMGd)fmV+bxMW>^?dXx4bG2g<3597=zZ7?PrY; z76lDSldcq%W+Yyz&+|(IX$8TAee&BoE=$vbQ;d?tzMRpFMk6W`N5qjQn zUjF5-%+#KY?vJ)rB)wZ3`V5;{d1Cj!Z|lj9JkKfmD)1!?<(z80NwvMa2UQ7s@4&5s z2L5$O9S-}7fo$O^01-bqLO6UH$du=RLsXbgQaZ~w;e_8P?RaB1M1}@)w+YLN46G-s z0rwv-$4!|Il|ulOzT7lI=|k6$8Gvgu0JtN3#kl#iMye;QF!A;p(NZH$stL^yI#3iu zpMpujkNm*3fuWGjo8|Lg*0jNJ8)CK>1$E=65RIyZKCfnVv{@y$Ahmi zr(+b3_OP*fs{CHWv^m7>fweY6&mY1vi8UoSgVvOHF=_bL^j5y=!Ta6|yAJdN9Q~4x zmQRGt%HcD>^q)C__FUHYTrMnYQ_gbYM>z3AiNxR4h#v!S!dhui{)6Cy!U0}lWxRxV zqRpU$wc1W`T*WZ%UtNkosMEqnK)IAW9ffu9gfbl!4$OI#RtAsq2D}4dC5`C`Jjy{J+KhO83DeJ``~9w(zY0(_!%57PTT+ z*S-!XH3*Z#*1OXWB3p{d5r$jPtw?f8OBi9s2HFX9$5 zzl~`?wsBybTbNESd`|oj9tGwLXXbbUH}yDwZHlq;*Kjjk=LRQbZsmuNLiax2KFx&t zI0rvYJUcur6(Y2asZx& zgJwN8d;EU9W93WV@dU6tqMOD^;|~IK<`7<0j+L_rpr+7Wqwl$YAC=nqIB3!jq|i$^ z+8rJ=Z*-}-I~Nu}K7RoU!?pX580vC-gQeVooD0tjXm?;bL9W*!%^1>w$plH$A-^Yx zNj~9GKL3NNhV!Ups_iyNW}u*~8+B`3Zq6;EkL|bJn0}y#!IoA2n0V>utfC00sC(*y z+>G>{X_()uSJ3xx4ZT0hH?Yy^0$`M0MtU9TL;XDlzbmjQwvS9W{X;CTOFQ;+JDtM) z)O8c|1e5lDw2#;j_q|RiU3AEELTNzHcnlTmg=NMJ%fcnDC6!B-y5_{ok`3u5Boh){U9smZ#vVd?5@|ouaisT= zhLCbfU{Ua#gQpY8kJN+ocS!q?`j9?CI*YU%bn}r4k#0gVAzdtHY#MOyL?P7FbroHh%gqWa8mdE0RNAa`hSp+0NVrD!+>4w`kyqPD-d7dAIjle%^3*zDP*^#X&tKr+!6GSfW2#|4YcAL z#16k#^v36rIq7aM1&w?Z@bdD=B728ZHu?S4LEi?17_S*YgE$%B2o7n5 z*lo$qK-F*|X2m;?qAtY$ww!^D-XMK4xdvq@X6*gMD{Yg?6ggPCaE%Dw>i{Z7X z9GcM1(qaV5fhRdpn-cPiL*9^=0;{#6cRjNJ5|eHm$XWqKfLp6kW6T-w(KjiK{jcPD z(O=ntkYCHvc3(3rHCn+17JMldgV=MJmhwFUgE9JCO!=)!1Q8Rt{vd$Y^ z63{ExtNz=R&JMI8wz7>Wt2cDE>o2Tb40PTTZ1>OfcEMH~pcA@9b~M3^>mR9-1~uOs z@&&yK&_(R-R-HCJ+zSBHDdVsTSbfN%Dip*piM7Fd{aEWN7tCz&`w=s$@nIy^`8pw* zI$yi5Mbjo2(W(WNF4s&~1LBYWuP+0)kFbT^BU>h~6#v1x3VaM~TMck)<}lU*`!@P& zNyg&I+=wEt#@){9k*j5k*h0M5;_g76u9uCUUZGK-e_vv?#ec!HC)sUms+kc#vagBP zVF0iRU?PuCx3hJq&4(zp7d5xDRu%y9qnsw6*}(m5BpX%>^aFA2n^3k2H0xOhV%zO( z6S!%@8FK^+Ia*{^|>W=Y3 z(ezHl(kX`C0bYHehMMCN(OxH3DXmotTL9cz&@~}#0)83(Av#=-o7di%P`-%GWCcit z_`@afG?VkSmW4p$N9`8Wwh4bkwdlRkIA*K_E17}INaS_4qeXv0?=`;G>3q!ukDHKa z@Mogj0+b<~pG&U;IO^Ft?CnI*`~PJA!YDQSF&iPwtm0Ys z&ia>GpUs+7@~x7(lC>pWB~O;TIj3lD`P`**Tju)99xZ#N>{n&KEgM^YWBISk)8}Q+ zn>(+5Uc&+C@>e=3YQmt zr|{Ln(}j;&`>cPoGTW`TIksiCF5CUK@7a27pV&ULEh}m++E(cH z6#HjAI4iy6?UK(+j?X?f`yc1@&Z(Q*JooLnf1cY|DwgJyZ75UAYRgZRe_rmJw|gGc zl98+o`M2ip0Vk6SW)?hA@biMTg%1`!QFx&6KI@~_7p#Y^ry;o{+jB*46unn;xyV#} zU2#$IoZ_0|HO2Q7pD!La>)W%I&)PEU*;%j6Iy38*lKV@(Q*xr@_a)LX`mAXq;ly;PEEPcH6C#C;fdZzUA(xKAivaw}1l+7wLmQO8rmfv6g zX!*~}e^Np_6u8-jQ;<>{t5~F7uSShape of a panel group. +public enum PanelGroupKind +{ + /// A 4×2 MFD button cluster (lamp buttons). + Mfd, + + /// A vertical 1×8 board column (lamp buttons). + Column, + + /// A 4×4 keypad (added later; no lamps). + Keypad, +} + +/// +/// One functional group on the cockpit control panel: a titled block of address +/// buttons laid out in × (row-major, null = +/// empty slot). / place the block on +/// the panel in button-cell units (the renderer scales cells to pixels). +/// +public sealed record PanelGroup( + string Title, + PanelGroupKind Kind, + int Cols, + int Rows, + bool LampCapable, + int OriginCol, + int OriginRow, + IReadOnlyList Addresses); + +/// One address button placed on the panel (absolute cell coordinates). +public sealed record PanelButton(int Address, PanelGroup Group, int Col, int Row, bool LampCapable); + +/// +/// The cockpit control panel's logical layout — the functional grouping from the +/// original Win32 RIO design (docs/Win32RIO/control-panel-mockup.png): five MFD +/// clusters, four board columns, and the two later-added 4×4 keypads. Every RIO +/// address 0x00–0x47 / 0x50–0x6F is placed exactly once. Pure data so the mapping +/// is unit-tested; the WinForms editor renders these groups and binds each button. +/// +public static class CockpitPanel +{ + // Helper: 4×2 MFD address block, top row then bottom row (addresses descend). + private static int?[] Mfd(int hi) => new int?[] + { + hi, hi - 1, hi - 2, hi - 3, + hi - 4, hi - 5, hi - 6, hi - 7, + }; + + // Helper: vertical 1×8 column from a base address upward. + private static int?[] Col8(int baseAddr) => new int?[] + { + baseAddr, baseAddr + 1, baseAddr + 2, baseAddr + 3, + baseAddr + 4, baseAddr + 5, baseAddr + 6, baseAddr + 7, + }; + + // Helper: 4×4 keypad. Physical keys 1 2 3 C / 4 5 6 D / 7 8 9 E / A 0 B F map to + // address = base + that hex digit. + private static int?[] Keypad(int baseAddr) + { + int[] digits = { 0x1, 0x2, 0x3, 0xC, 0x4, 0x5, 0x6, 0xD, 0x7, 0x8, 0x9, 0xE, 0xA, 0x0, 0xB, 0xF }; + var a = new int?[16]; + for (int i = 0; i < 16; i++) a[i] = baseAddr + digits[i]; + return a; + } + + /// All panel groups, positioned for rendering. + public static IReadOnlyList Groups { get; } = new[] + { + // Upper MFD row. + new PanelGroup("Upper Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 1, Mfd(0x2F)), + new PanelGroup("Upper Middle MFD", PanelGroupKind.Mfd, 4, 2, true, 5, 1, Mfd(0x27)), + new PanelGroup("Upper Right MFD", PanelGroupKind.Mfd, 4, 2, true, 10, 1, Mfd(0x37)), + + // Lower MFD row. + new PanelGroup("Lower Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 5, Mfd(0x0F)), + new PanelGroup("Lower Right MFD", PanelGroupKind.Mfd, 4, 2, true, 10, 5, Mfd(0x07)), + + // Board columns. + new PanelGroup("Throttle", PanelGroupKind.Column, 1, 8, true, 0, 9, Col8(0x38)), + new PanelGroup("Secondary", PanelGroupKind.Column, 1, 8, true, 5, 9, Col8(0x10)), + new PanelGroup("Screen", PanelGroupKind.Column, 1, 8, true, 7, 9, Col8(0x18)), + new PanelGroup("Joystick / Hat", PanelGroupKind.Column, 1, 8, true, 13, 9, Col8(0x40)), + + // Keypads (no lamps): internal in the middle (below Upper Middle MFD, above + // the Secondary/Screen columns); external between those columns and Joystick. + new PanelGroup("Internal Keypad", PanelGroupKind.Keypad, 4, 4, false, 5, 4, Keypad(0x50)), + new PanelGroup("External Keypad", PanelGroupKind.Keypad, 4, 4, false, 9, 9, Keypad(0x60)), + }; + + /// Flatten the groups to positioned s (absolute cells). + public static IReadOnlyList Buttons() + { + var buttons = new List(); + foreach (PanelGroup g in Groups) + { + for (int i = 0; i < g.Addresses.Count; i++) + { + if (g.Addresses[i] is not { } addr) continue; + int localCol = i % g.Cols; + int localRow = i / g.Cols; + // +1 row leaves space under the title. + buttons.Add(new PanelButton(addr, g, g.OriginCol + localCol, g.OriginRow + 1 + localRow, g.LampCapable)); + } + } + return buttons; + } +} diff --git a/src/RioJoy.Core/Mapping/ButtonBinding.cs b/src/RioJoy.Core/Mapping/ButtonBinding.cs index d47c638..679017f 100644 --- a/src/RioJoy.Core/Mapping/ButtonBinding.cs +++ b/src/RioJoy.Core/Mapping/ButtonBinding.cs @@ -34,6 +34,39 @@ public sealed class ButtonBinding /// True when this binding does nothing (an empty slot). public bool IsUnmapped => ToWord() == 0; + /// + /// A short human-readable description of the assigned function — e.g. + /// CTL-N, JOY1, POV-U, Mse LB, or a RIO command name. + /// Empty when unmapped. Used as the editor cell caption. + /// + public string Describe() => Kind switch + { + _ when IsUnmapped => string.Empty, + RioRouteKind.Keyboard => + (Ctrl ? "CTL-" : "") + (Shift ? "SFT-" : "") + (Alt ? "ALT-" : "") + KeyCatalog.NameFor(Value), + RioRouteKind.Joystick => $"JOY{Value}", + RioRouteKind.Hat => (RioHat)Value switch + { + RioHat.Up => "POV-U", + RioHat.Right => "POV-R", + RioHat.Down => "POV-D", + RioHat.Left => "POV-L", + _ => "POV", + }, + RioRouteKind.Mouse => "Mse " + (MouseAction)Value switch + { + MouseAction.MoveUp => "Up", + MouseAction.MoveDown => "Dn", + MouseAction.MoveLeft => "Lt", + MouseAction.MoveRight => "Rt", + MouseAction.LeftClick => "LB", + MouseAction.RightClick => "RB", + _ => Value.ToString(), + }, + RioRouteKind.RioCommand => ((RioCommandCode)Value).ToString(), + _ => string.Empty, + }; + /// Pack to the 16-bit iRIO map word. public ushort ToWord() => RioMapEntry.Create(Kind, Value, IsLit, Shift, Ctrl, Alt, Extended).Raw; diff --git a/src/RioJoy.Tray/Editor/PanelCanvas.cs b/src/RioJoy.Tray/Editor/PanelCanvas.cs new file mode 100644 index 0000000..774a877 --- /dev/null +++ b/src/RioJoy.Tray/Editor/PanelCanvas.cs @@ -0,0 +1,67 @@ +using System.Runtime.Versioning; +using RioJoy.Core.Editing; + +namespace RioJoy.Tray.Editor; + +/// +/// Interactive cockpit control-panel map: draws the functional groups (via +/// ) and raises when a button +/// is clicked. Sized to the full panel so the host scrolls it. +/// +[SupportedOSPlatform("windows")] +internal sealed class PanelCanvas : Control +{ + private static readonly IReadOnlyList AllButtons = CockpitPanel.Buttons(); + + public PanelCanvas() + { + DoubleBuffered = true; + BackColor = Color.FromArgb(28, 28, 28); + Size = PanelView.GridSize(AllButtons); + } + + /// Resolves the display label for an address (from the profile). + public Func? LabelProvider { get; set; } + + /// Resolves the assigned-function caption for an address (empty = unmapped). + public Func? FunctionProvider { get; set; } + + /// Whether an address's lamp is lit (the binding's IsLit flag) — drives the lamp shade. + public Func? LitProvider { get; set; } + + public int? SelectedAddress { get; private set; } + + public event Action? AddressSelected; + + public void Select(int? address) + { + SelectedAddress = address; + Invalidate(); + } + + public void Refresh(int? address) + { + SelectedAddress = address; + Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) => + PanelView.Paint( + e.Graphics, AllButtons, CockpitPanel.Groups, + a => LabelProvider?.Invoke(a), + a => FunctionProvider?.Invoke(a), + a => LitProvider?.Invoke(a) ?? false, + SelectedAddress); + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + PanelButton? hit = PanelView.HitTest(AllButtons, e.Location); + if (hit is not null) + { + SelectedAddress = hit.Address; + AddressSelected?.Invoke(hit.Address); + Invalidate(); + } + } +} diff --git a/src/RioJoy.Tray/Editor/PanelView.cs b/src/RioJoy.Tray/Editor/PanelView.cs new file mode 100644 index 0000000..d5fd8c0 --- /dev/null +++ b/src/RioJoy.Tray/Editor/PanelView.cs @@ -0,0 +1,135 @@ +using System.Runtime.Versioning; +using RioJoy.Core.Editing; + +namespace RioJoy.Tray.Editor; + +/// +/// Draws the cockpit control panel — the functional groups from +/// (MFD clusters, board columns, keypads) plus the +/// encoder-gauge strip — mirroring the original Win32 RIO design. Lamp buttons are +/// shaded by whether the address is assigned (Off vs Dim); keypads are neutral +/// (no lamps). Shared by and offline rendering. +/// +[SupportedOSPlatform("windows")] +public static class PanelView +{ + public const int CellW = 66; + public const int CellH = 34; + public const int TopStrip = 92; // encoder-gauge band height + + public static Size GridSize(IReadOnlyList buttons) + { + int maxCol = 0, maxRow = 0; + foreach (PanelButton b in buttons) + { + if (b.Col > maxCol) maxCol = b.Col; + if (b.Row > maxRow) maxRow = b.Row; + } + return new Size((maxCol + 2) * CellW, TopStrip + (maxRow + 2) * CellH); + } + + private static Rectangle Cell(int col, int row) => + new(col * CellW + 2, TopStrip + row * CellH + 2, CellW - 4, CellH - 4); + + public static void Paint( + Graphics g, + IReadOnlyList buttons, + IReadOnlyList groups, + Func label, + Func function, + Func lit, + int? selected) + { + g.Clear(Color.FromArgb(28, 28, 28)); + using var titleFont = new Font("Segoe UI", 8f, FontStyle.Bold); + using var btnFont = new Font("Segoe UI", 7.5f); + using var subFont = new Font("Segoe UI", 6.75f); + using var groupPen = new Pen(Color.FromArgb(80, 80, 80)); + + DrawEncoderStrip(g, titleFont); + + // Group frames + titles. + foreach (PanelGroup grp in groups) + { + var frame = new Rectangle( + grp.OriginCol * CellW + 1, + TopStrip + grp.OriginRow * CellH + 1, + grp.Cols * CellW, + (grp.Rows + 1) * CellH); + g.DrawRectangle(groupPen, frame); + TextRenderer.DrawText(g, grp.Title, titleFont, + new Rectangle(frame.X + 2, frame.Y, frame.Width - 2, CellH), Color.Gainsboro, + TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis); + } + + // Buttons: caption = label (top) + assigned function (bottom). + const TextFormatFlags oneLine = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | + TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix; + foreach (PanelButton b in buttons) + { + Rectangle r = Cell(b.Col, b.Row); + string? lab = label(b.Address); + string? fn = function(b.Address); + bool assigned = !string.IsNullOrEmpty(fn); + + Color fill = !b.LampCapable + ? Color.FromArgb(150, 182, 226) // keypad — neutral blue + : lit(b.Address) + ? Color.FromArgb(215, 60, 60) // lamp ON (IsLit checked) + : Color.FromArgb(86, 48, 48); // lamp OFF + using (var fb = new SolidBrush(fill)) g.FillRectangle(fb, r); + + Color fg = b.LampCapable ? Color.White : Color.Black; + bool hasLab = !string.IsNullOrEmpty(lab); + + if (hasLab && assigned) + { + TextRenderer.DrawText(g, lab, btnFont, new Rectangle(r.X, r.Y + 1, r.Width, r.Height / 2), fg, + TextFormatFlags.HorizontalCenter | TextFormatFlags.Top | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix); + TextRenderer.DrawText(g, fn, subFont, new Rectangle(r.X, r.Bottom - r.Height / 2 - 1, r.Width, r.Height / 2), fg, + TextFormatFlags.HorizontalCenter | TextFormatFlags.Bottom | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix); + } + else + { + string single = hasLab ? lab! : assigned ? fn! : $"{b.Address:X2}"; + TextRenderer.DrawText(g, single, btnFont, r, fg, oneLine); + } + + if (b.Address == selected) + { + using var hi = new Pen(Color.Yellow, 2f); + g.DrawRectangle(hi, r.X, r.Y, r.Width - 1, r.Height - 1); + } + } + } + + private static void DrawEncoderStrip(Graphics g, Font font) + { + using var pen = new Pen(Color.FromArgb(120, 160, 120)); + using var gauge = new SolidBrush(Color.FromArgb(20, 40, 20)); + + (string Label, int X, int W)[] boxes = + { + ("X / Y", 4, 80), + ("Z", 92, 26), + ("L", 122, 26), + ("R", 152, 26), + }; + foreach (var (lbl, x, w) in boxes) + { + var r = new Rectangle(x, 6, w, TopStrip - 24); + g.FillRectangle(gauge, r); + g.DrawRectangle(pen, r); + TextRenderer.DrawText(g, lbl, font, new Rectangle(x, TopStrip - 18, w, 16), Color.Gainsboro, + TextFormatFlags.HorizontalCenter); + } + } + + public static PanelButton? HitTest(IReadOnlyList buttons, Point p) + { + foreach (PanelButton b in buttons) + if (Cell(b.Col, b.Row).Contains(p)) + return b; + return null; + } +} diff --git a/src/RioJoy.Tray/Editor/ProfileEditorForm.cs b/src/RioJoy.Tray/Editor/ProfileEditorForm.cs index c0c65e5..3d65fb7 100644 --- a/src/RioJoy.Tray/Editor/ProfileEditorForm.cs +++ b/src/RioJoy.Tray/Editor/ProfileEditorForm.cs @@ -7,9 +7,10 @@ using RioJoy.Core.Profiles; namespace RioJoy.Tray.Editor; /// -/// Profile editor: shows the cockpit authoring sheet (its logical layout, from -/// ) as a scrollable, clickable grid and edits the -/// selected address cell's label and action/modifiers/lamp. Writes back to the +/// Profile editor: shows the cockpit control panel (the functional grouping from +/// the original Win32 RIO design — MFD clusters, board columns, keypads, encoder +/// gauges, via ) as a clickable map, and edits the +/// selected button's label and action/modifiers/lamp. Writes back to the /// 's (keyed by the /// wallpaper region b-XX) and (the iRIO /// word); Save raises . @@ -17,9 +18,14 @@ namespace RioJoy.Tray.Editor; [SupportedOSPlatform("windows")] public sealed class ProfileEditorForm : Form { + private static readonly Dictionary GroupOf = + CockpitPanel.Buttons().ToDictionary(b => b.Address, b => b.Group.Title); + + private static readonly Dictionary LampCapableOf = + CockpitPanel.Buttons().ToDictionary(b => b.Address, b => b.LampCapable); + private readonly RioProfile _profile; - private readonly IReadOnlyList _cells; - private readonly SheetCanvas _canvas = new(); + private readonly PanelCanvas _canvas = new(); private readonly Label _info = new() { AutoSize = true, Location = new Point(12, 12), MaximumSize = new Size(310, 0) }; private readonly TextBox _labelBox = new() { Location = new Point(70, 44), Width = 230 }; @@ -32,6 +38,7 @@ public sealed class ProfileEditorForm : Form private readonly CheckBox _ext = new() { Text = "Ext", Location = new Point(250, 144), AutoSize = true }; private readonly CheckBox _lit = new() { Text = "Lit", Location = new Point(70, 172), AutoSize = true }; private readonly Button _apply = new() { Text = "Apply to cell", Location = new Point(70, 206), Width = 110 }; + private readonly Button _unassign = new() { Text = "Unassign", Location = new Point(190, 206), Width = 110 }; private readonly Button _save = new() { Text = "Save profile", Location = new Point(70, 240), Width = 110 }; private readonly Button _close = new() { Text = "Close", Location = new Point(190, 240), Width = 80 }; @@ -47,19 +54,21 @@ public sealed class ProfileEditorForm : Form /// Raised when the user saves; the argument is the edited profile. public event Action? Saved; - public ProfileEditorForm(IReadOnlyList cells, RioProfile profile) + public ProfileEditorForm(RioProfile profile) { - _cells = cells ?? throw new ArgumentNullException(nameof(cells)); _profile = profile ?? throw new ArgumentNullException(nameof(profile)); Text = $"RIOJoy — Edit profile: {profile.Name}"; - ClientSize = new Size(1500, 820); + ClientSize = new Size(1320, 820); StartPosition = FormStartPosition.CenterScreen; MinimumSize = new Size(900, 500); _kindBox.Items.AddRange(Enum.GetNames()); _kindBox.SelectedIndexChanged += OnKindChanged; - _canvas.SetCells(cells); + + _canvas.LabelProvider = a => _profile.OverlayLabels.TryGetValue(RegionFor(a), out string? t) ? t : null; + _canvas.FunctionProvider = a => _profile.Buttons.TryGetValue(a, out int w) ? ButtonBinding.FromWord((ushort)w).Describe() : null; + _canvas.LitProvider = a => _profile.Buttons.TryGetValue(a, out int w) && ButtonBinding.FromWord((ushort)w).IsLit; _canvas.AddressSelected += OnAddressSelected; var scroller = new Panel { Dock = DockStyle.Fill, AutoScroll = true, BackColor = Color.FromArgb(28, 28, 28) }; @@ -71,11 +80,12 @@ public sealed class ProfileEditorForm : Form Controls.Add(editPanel); _apply.Click += (_, _) => ApplyToCell(); + _unassign.Click += (_, _) => Unassign(); _save.Click += (_, _) => { ApplyToCell(); Saved?.Invoke(_profile); }; _close.Click += (_, _) => Close(); SetEditingEnabled(false); - _info.Text = "Click a coloured address cell to edit it."; + _info.Text = "Click a button to edit it."; } /// The selected RIO address (for tests / offline rendering). @@ -92,25 +102,23 @@ public sealed class ProfileEditorForm : Form panel.Controls.Add(_kindBox); panel.Controls.Add(_valueLabel); panel.Controls.Add(_valueCombo); - panel.Controls.AddRange(new Control[] { _shift, _ctrl, _alt, _ext, _lit, _apply, _save, _close }); + panel.Controls.AddRange(new Control[] { _shift, _ctrl, _alt, _ext, _lit, _apply, _unassign, _save, _close }); return panel; } - private void OnAddressSelected(SheetCell cell) + private void OnAddressSelected(int addr) { - if (cell.Address is not { } addr) { SetEditingEnabled(false); return; } - _address = addr; string region = RegionFor(addr); - _info.Text = $"Cell {cell.Text} Address 0x{addr:X2} {cell.Section} (wallpaper {region})"; - - _labelBox.Text = _profile.OverlayLabels.TryGetValue(region, out string? label) ? label : string.Empty; + string group = GroupOf.TryGetValue(addr, out string? gn) ? gn : "—"; + _info.Text = $"Address 0x{addr:X2} {group} (wallpaper {region})"; ButtonBinding b = ButtonBinding.FromWord( _profile.Buttons.TryGetValue(addr, out int w) ? (ushort)w : (ushort)0); _loading = true; + _labelBox.Text = _profile.OverlayLabels.TryGetValue(region, out string? label) ? label : string.Empty; _kindBox.SelectedItem = b.Kind.ToString(); PopulateValues(b.Kind); SelectValue(b.Value); @@ -119,6 +127,7 @@ public sealed class ProfileEditorForm : Form _alt.Checked = b.Alt; _ext.Checked = b.Extended; _lit.Checked = b.IsLit; + _lit.Visible = LampCapableOf.GetValueOrDefault(addr); // keypads have no lamp _loading = false; SetEditingEnabled(true); @@ -147,7 +156,6 @@ public sealed class ProfileEditorForm : Form _valueCombo.BeginUpdate(); _valueCombo.Items.Clear(); - switch (kind) { case RioRouteKind.Keyboard: @@ -171,7 +179,6 @@ public sealed class ProfileEditorForm : Form _valueCombo.Items.Add(new ValueItem(c.ToString(), (byte)c)); break; } - _valueCombo.EndUpdate(); UpdateModifierAvailability(); } @@ -186,12 +193,10 @@ public sealed class ProfileEditorForm : Form return; } } - // Not in the list (e.g. an uncatalogued VK): add a raw fallback entry. int idx = _valueCombo.Items.Add(new ValueItem($"0x{value:X2}", value)); _valueCombo.SelectedIndex = idx; } - // Modifiers/extended only make sense for keyboard actions. private void UpdateModifierAvailability() { bool keyboard = SelectedKind() == RioRouteKind.Keyboard; @@ -223,7 +228,7 @@ public sealed class ProfileEditorForm : Form Ctrl = keyboard && _ctrl.Checked, Alt = keyboard && _alt.Checked, Extended = keyboard && _ext.Checked, - IsLit = _lit.Checked, + IsLit = LampCapableOf.GetValueOrDefault(addr) && _lit.Checked, // keypads never lit }; ushort word = binding.ToWord(); @@ -231,11 +236,29 @@ public sealed class ProfileEditorForm : Form _profile.Buttons.Remove(addr); else _profile.Buttons[addr] = word; + + _canvas.Refresh(addr); // update lamp shade + label on the panel + } + + // Clear the selected button's action mapping (the label is left untouched). + private void Unassign() + { + if (_address is not { } addr) return; + _profile.Buttons.Remove(addr); + + _loading = true; + _kindBox.SelectedItem = RioRouteKind.Keyboard.ToString(); + PopulateValues(RioRouteKind.Keyboard); + if (_valueCombo.Items.Count > 0) _valueCombo.SelectedIndex = 0; + _shift.Checked = _ctrl.Checked = _alt.Checked = _ext.Checked = _lit.Checked = false; + _loading = false; + + _canvas.Refresh(addr); } private void SetEditingEnabled(bool enabled) { - foreach (Control c in new Control[] { _labelBox, _kindBox, _valueCombo, _shift, _ctrl, _alt, _ext, _lit, _apply }) + foreach (Control c in new Control[] { _labelBox, _kindBox, _valueCombo, _shift, _ctrl, _alt, _ext, _lit, _apply, _unassign }) c.Enabled = enabled; if (enabled) UpdateModifierAvailability(); } @@ -245,11 +268,7 @@ public sealed class ProfileEditorForm : Form /// Select an address programmatically (for tests / offline preview). public void SelectAddress(int address) { - SheetCell? cell = _cells.FirstOrDefault(c => c.Address == address); - if (cell is not null) - { - _canvas.Select(address); - OnAddressSelected(cell); - } + _canvas.Select(address); + OnAddressSelected(address); } } diff --git a/src/RioJoy.Tray/Editor/SheetCanvas.cs b/src/RioJoy.Tray/Editor/SheetCanvas.cs deleted file mode 100644 index 0a070f8..0000000 --- a/src/RioJoy.Tray/Editor/SheetCanvas.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Runtime.Versioning; -using RioJoy.Core.Editing; - -namespace RioJoy.Tray.Editor; - -/// -/// Scrollable cockpit-sheet grid: draws the cells (via ) at a -/// fixed cell size and raises when an address cell is -/// clicked. Sized to the full grid so the host panel scrolls it. -/// -[SupportedOSPlatform("windows")] -internal sealed class SheetCanvas : Control -{ - private const int CellW = 62; - private const int CellH = 22; - - private IReadOnlyList _cells = Array.Empty(); - - public SheetCanvas() - { - DoubleBuffered = true; - BackColor = Color.FromArgb(28, 28, 28); - } - - /// The RIO address currently highlighted, or null. - public int? SelectedAddress { get; private set; } - - public event Action? AddressSelected; - - public void SetCells(IReadOnlyList cells) - { - _cells = cells; - Size = SheetView.GridSize(cells, CellW, CellH); - Invalidate(); - } - - public void Select(int? address) - { - SelectedAddress = address; - Invalidate(); - } - - protected override void OnPaint(PaintEventArgs e) => - SheetView.Paint(e.Graphics, _cells, CellW, CellH, SelectedAddress); - - protected override void OnMouseDown(MouseEventArgs e) - { - base.OnMouseDown(e); - SheetCell? hit = SheetView.HitTest(_cells, e.Location, CellW, CellH); - if (hit?.Address is { } addr) - { - SelectedAddress = addr; - AddressSelected?.Invoke(hit); - Invalidate(); - } - } -} diff --git a/src/RioJoy.Tray/Editor/SheetView.cs b/src/RioJoy.Tray/Editor/SheetView.cs deleted file mode 100644 index 5c80ca9..0000000 --- a/src/RioJoy.Tray/Editor/SheetView.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Runtime.Versioning; -using RioJoy.Core.Editing; - -namespace RioJoy.Tray.Editor; - -/// -/// Draws the cockpit authoring sheet as a grid of cells at their spreadsheet -/// (row, col) positions — the cockpit's logical layout. Address cells are filled by -/// section and clickable; scaffolding/label cells are drawn as context text. Shared -/// by and offline bitmap rendering. -/// -[SupportedOSPlatform("windows")] -public static class SheetView -{ - public static Color SectionColor(SheetSection section) => section switch - { - SheetSection.Contacts or SheetSection.Letters => Color.FromArgb(232, 150, 150), // red - SheetSection.Secondary or SheetSection.Screen => Color.FromArgb(245, 224, 150), // yellow - SheetSection.Throttle or SheetSection.Joystick => Color.FromArgb(150, 182, 226), // blue - SheetSection.Keypad or SheetSection.ExtKeypad => Color.FromArgb(165, 212, 165), // green - _ => Color.Gainsboro, - }; - - public static Size GridSize(IReadOnlyList cells, int cellW, int cellH) - { - int maxCol = 0, maxRow = 0; - foreach (SheetCell c in cells) - { - if (c.Col > maxCol) maxCol = c.Col; - if (c.Row > maxRow) maxRow = c.Row; - } - return new Size((maxCol + 1) * cellW, (maxRow + 1) * cellH); - } - - public static void Paint( - Graphics g, - IReadOnlyList cells, - int cellW, - int cellH, - int? selectedAddress) - { - g.Clear(Color.FromArgb(28, 28, 28)); - - using var addrFont = new Font("Segoe UI", 7.5f, FontStyle.Bold); - using var textFont = new Font("Segoe UI", 7f); - using var border = new Pen(Color.FromArgb(70, 70, 70)); - - foreach (SheetCell cell in cells) - { - var r = new Rectangle(cell.Col * cellW, cell.Row * cellH, cellW, cellH); - - if (cell.Address is { } addr) - { - using var fill = new SolidBrush(SectionColor(cell.Section)); - g.FillRectangle(fill, r); - g.DrawRectangle(border, r); - TextRenderer.DrawText(g, cell.Text, addrFont, r, Color.Black, - TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPrefix); - - if (addr == selectedAddress) - { - using var hi = new Pen(Color.Yellow, 2f); - g.DrawRectangle(hi, r.X + 1, r.Y + 1, r.Width - 2, r.Height - 2); - } - } - else - { - // Scaffolding / KEY / LABEL / section headers — context only. - TextRenderer.DrawText(g, cell.Text, textFont, r, Color.Silver, - TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | - TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix); - } - } - } - - /// The address cell at a pixel point, or null. - public static SheetCell? HitTest(IReadOnlyList cells, Point p, int cellW, int cellH) - { - int col = p.X / cellW; - int row = p.Y / cellH; - return cells.FirstOrDefault(c => c.Row == row && c.Col == col && c.Address is not null); - } -} diff --git a/src/RioJoy.Tray/TrayApplicationContext.cs b/src/RioJoy.Tray/TrayApplicationContext.cs index 53d466d..49de9d7 100644 --- a/src/RioJoy.Tray/TrayApplicationContext.cs +++ b/src/RioJoy.Tray/TrayApplicationContext.cs @@ -1,5 +1,4 @@ using System.Runtime.Versioning; -using RioJoy.Core.Editing; using RioJoy.Core.Mapping; using RioJoy.Core.Profiles; using RioJoy.Tray.Editor; @@ -135,35 +134,11 @@ internal sealed class TrayApplicationContext : ApplicationContext private void OpenEditor() { - // The cockpit sheet (config-sheet.csv) sits beside the overlay template. - string? templatePath = _config.OverlayTemplatePath; - string? sheetPath = string.IsNullOrWhiteSpace(templatePath) - ? null - : Path.Combine(Path.GetDirectoryName(Path.GetFullPath(templatePath)) ?? ".", "config-sheet.csv"); - - if (sheetPath is null || !File.Exists(sheetPath)) - { - MessageBox.Show( - "Set 'OverlayTemplatePath' in the config and place 'config-sheet.csv' beside it to use the editor.", - "RIOJoy", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - IReadOnlyList cells; - try - { - cells = SheetLayout.Load(sheetPath); - } - catch (Exception ex) - { - MessageBox.Show($"Could not load the cockpit sheet:\n{ex.Message}", "RIOJoy", - MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - + // The cockpit panel layout is built in (CockpitPanel); the editor just needs + // a profile to edit. RioProfile profile = _config.Profiles.FirstOrDefault() ?? AddBlankProfile(); - var editor = new ProfileEditorForm(cells, profile); + var editor = new ProfileEditorForm(profile); editor.Saved += _ => ConfigStore.Save(_config, ConfigPath); editor.Show(); } diff --git a/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs b/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs new file mode 100644 index 0000000..685ac1c --- /dev/null +++ b/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs @@ -0,0 +1,68 @@ +using RioJoy.Core.Editing; +using RioJoy.Core.Mapping; +using Xunit; + +namespace RioJoy.Core.Tests.Editing; + +public class CockpitPanelTests +{ + [Fact] + public void Covers_EveryValidAddress_ExactlyOnce() + { + List addrs = CockpitPanel.Buttons().Select(b => b.Address).ToList(); + + // 0x00–0x47 buttons + 0x50–0x6F keypads = 104 valid addresses; 0x48–0x4F don't exist. + var expected = Enumerable.Range(0x00, 0x48).Concat(Enumerable.Range(0x50, 0x20)).ToHashSet(); + Assert.Equal(expected.Count, addrs.Count); // no duplicates / no extras + Assert.Equal(expected, addrs.ToHashSet()); // exactly the valid set + Assert.DoesNotContain(0x48, addrs); // gap excluded + } + + [Theory] + [InlineData(0x2F, "Upper Left MFD")] + [InlineData(0x20, "Upper Middle MFD")] + [InlineData(0x37, "Upper Right MFD")] + [InlineData(0x00, "Lower Right MFD")] + [InlineData(0x0F, "Lower Left MFD")] + [InlineData(0x38, "Throttle")] + [InlineData(0x10, "Secondary")] + [InlineData(0x18, "Screen")] + [InlineData(0x40, "Joystick / Hat")] + [InlineData(0x50, "Internal Keypad")] + [InlineData(0x6F, "External Keypad")] + public void Address_LandsInExpectedGroup(int address, string group) + { + PanelButton b = CockpitPanel.Buttons().Single(x => x.Address == address); + Assert.Equal(group, b.Group.Title); + } + + [Fact] + public void Keypads_AreNotLampCapable() + { + foreach (PanelButton b in CockpitPanel.Buttons().Where(x => x.Group.Kind == PanelGroupKind.Keypad)) + Assert.False(b.LampCapable); + // ...and everything else is. + foreach (PanelButton b in CockpitPanel.Buttons().Where(x => x.Group.Kind != PanelGroupKind.Keypad)) + Assert.True(b.LampCapable); + } + + [Fact] + public void Keypad_PhysicalLayout_MapsDigitsToAddresses() + { + // Internal keypad: top-left "1" -> 0x51, bottom-middle "0" -> 0x50, "C" -> 0x5C. + IReadOnlyList all = CockpitPanel.Buttons(); + PanelGroup pad = CockpitPanel.Groups.Single(g => g.Title == "Internal Keypad"); + + Assert.Equal(0x51, pad.Addresses[0]); // row0 col0 = "1" + Assert.Equal(0x5C, pad.Addresses[3]); // row0 col3 = "C" + Assert.Equal(0x50, pad.Addresses[13]); // row3 col1 = "0" + Assert.Equal(0x5F, pad.Addresses[15]); // row3 col3 = "F" + } + + [Fact] + public void AllAddresses_AreValidRioAddresses() + { + foreach (PanelButton b in CockpitPanel.Buttons()) + Assert.InRange(b.Address, 0, RioAddress.MaxAddress); + } +} diff --git a/tests/RioJoy.Core.Tests/Mapping/ButtonBindingTests.cs b/tests/RioJoy.Core.Tests/Mapping/ButtonBindingTests.cs index 5d67faa..51b14b7 100644 --- a/tests/RioJoy.Core.Tests/Mapping/ButtonBindingTests.cs +++ b/tests/RioJoy.Core.Tests/Mapping/ButtonBindingTests.cs @@ -81,4 +81,17 @@ public class ButtonBindingTests Assert.True(new ButtonBinding().IsUnmapped); Assert.False(new ButtonBinding { Value = 0x41 }.IsUnmapped); } + + [Fact] + public void Describe_FormatsEachKind() + { + Assert.Equal("", new ButtonBinding().Describe()); + Assert.Equal("K", new ButtonBinding { Value = 0x4B }.Describe()); + Assert.Equal("CTL-N", new ButtonBinding { Value = 0x4E, Ctrl = true }.Describe()); + Assert.Equal("CTL-SFT-K", new ButtonBinding { Value = 0x4B, Ctrl = true, Shift = true }.Describe()); + Assert.Equal("JOY1", new ButtonBinding { Kind = RioRouteKind.Joystick, Value = 1 }.Describe()); + Assert.Equal("POV-U", new ButtonBinding { Kind = RioRouteKind.Hat, Value = (byte)(int)RioHat.Up }.Describe()); + Assert.Equal("Mse LB", new ButtonBinding { Kind = RioRouteKind.Mouse, Value = (byte)MouseAction.LeftClick }.Describe()); + Assert.Equal("ResetThrottle", new ButtonBinding { Kind = RioRouteKind.RioCommand, Value = (byte)RioCommandCode.ResetThrottle }.Describe()); + } }