From 5219615418920be8502aa24507572cf0930d65ea Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Aug 2020 07:39:24 -0400 Subject: [PATCH 01/12] =?UTF-8?q?Project=20Mj=C3=B6lnir:=20Part=202=20-=20?= =?UTF-8?q?Controller=20Applet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Its-Rei <kupfel@gmail.com> --- dist/icons/controller/applet_dual_joycon.png | Bin 0 -> 3554 bytes .../controller/applet_dual_joycon_dark.png | Bin 0 -> 3554 bytes .../applet_dual_joycon_dark_disabled.png | Bin 0 -> 3527 bytes .../applet_dual_joycon_disabled.png | Bin 0 -> 3314 bytes .../applet_dual_joycon_midnight.png | Bin 0 -> 3549 bytes .../applet_dual_joycon_midnight_disabled.png | Bin 0 -> 3584 bytes dist/icons/controller/applet_handheld.png | Bin 0 -> 1671 bytes .../icons/controller/applet_handheld_dark.png | Bin 0 -> 1637 bytes .../applet_handheld_dark_disabled.png | Bin 0 -> 2642 bytes .../controller/applet_handheld_disabled.png | Bin 0 -> 2221 bytes .../controller/applet_handheld_midnight.png | Bin 0 -> 1644 bytes .../applet_handheld_midnight_disabled.png | Bin 0 -> 2634 bytes .../controller/applet_pro_controller.png | Bin 0 -> 4382 bytes .../controller/applet_pro_controller_dark.png | Bin 0 -> 4236 bytes .../applet_pro_controller_dark_disabled.png | Bin 0 -> 4477 bytes .../applet_pro_controller_disabled.png | Bin 0 -> 4173 bytes .../applet_pro_controller_midnight.png | Bin 0 -> 4376 bytes ...pplet_pro_controller_midnight_disabled.png | Bin 0 -> 4459 bytes .../controller/applet_single_joycon_left.png | Bin 0 -> 2083 bytes .../applet_single_joycon_left_dark.png | Bin 0 -> 2067 bytes ...pplet_single_joycon_left_dark_disabled.png | Bin 0 -> 2520 bytes .../applet_single_joycon_left_disabled.png | Bin 0 -> 2179 bytes .../applet_single_joycon_left_midnight.png | Bin 0 -> 2065 bytes ...t_single_joycon_left_midnight_disabled.png | Bin 0 -> 2529 bytes .../controller/applet_single_joycon_right.png | Bin 0 -> 2150 bytes .../applet_single_joycon_right_dark.png | Bin 0 -> 2146 bytes ...plet_single_joycon_right_dark_disabled.png | Bin 0 -> 2556 bytes .../applet_single_joycon_right_disabled.png | Bin 0 -> 2212 bytes .../applet_single_joycon_right_midnight.png | Bin 0 -> 2150 bytes ..._single_joycon_right_midnight_disabled.png | Bin 0 -> 2611 bytes dist/icons/controller/controller.qrc | 30 + dist/qt_themes/default/style.qss | 206 +- dist/qt_themes/qdarkstyle/style.qss | 304 ++- .../qdarkstyle_midnight_blue/style.qss | 270 +- src/core/CMakeLists.txt | 4 + src/core/frontend/applets/controller.cpp | 40 + src/core/frontend/applets/controller.h | 45 + src/core/hle/service/am/applets/applets.cpp | 79 +- src/core/hle/service/am/applets/applets.h | 19 +- .../hle/service/am/applets/controller.cpp | 197 ++ src/core/hle/service/am/applets/controller.h | 119 + src/core/hle/service/hid/controllers/npad.cpp | 28 +- src/core/hle/service/hid/controllers/npad.h | 6 +- src/yuzu/CMakeLists.txt | 12 +- src/yuzu/applets/controller.cpp | 568 ++++ src/yuzu/applets/controller.h | 125 + src/yuzu/applets/controller.ui | 2432 +++++++++++++++++ src/yuzu/configuration/configure_input.cpp | 21 +- src/yuzu/configuration/configure_input.h | 2 +- .../configuration/configure_input_dialog.cpp | 37 + .../configuration/configure_input_dialog.h | 38 + .../configuration/configure_input_dialog.ui | 57 + src/yuzu/main.cpp | 35 +- src/yuzu/main.h | 6 + 54 files changed, 4526 insertions(+), 154 deletions(-) create mode 100644 dist/icons/controller/applet_dual_joycon.png create mode 100644 dist/icons/controller/applet_dual_joycon_dark.png create mode 100644 dist/icons/controller/applet_dual_joycon_dark_disabled.png create mode 100644 dist/icons/controller/applet_dual_joycon_disabled.png create mode 100644 dist/icons/controller/applet_dual_joycon_midnight.png create mode 100644 dist/icons/controller/applet_dual_joycon_midnight_disabled.png create mode 100644 dist/icons/controller/applet_handheld.png create mode 100644 dist/icons/controller/applet_handheld_dark.png create mode 100644 dist/icons/controller/applet_handheld_dark_disabled.png create mode 100644 dist/icons/controller/applet_handheld_disabled.png create mode 100644 dist/icons/controller/applet_handheld_midnight.png create mode 100644 dist/icons/controller/applet_handheld_midnight_disabled.png create mode 100644 dist/icons/controller/applet_pro_controller.png create mode 100644 dist/icons/controller/applet_pro_controller_dark.png create mode 100644 dist/icons/controller/applet_pro_controller_dark_disabled.png create mode 100644 dist/icons/controller/applet_pro_controller_disabled.png create mode 100644 dist/icons/controller/applet_pro_controller_midnight.png create mode 100644 dist/icons/controller/applet_pro_controller_midnight_disabled.png create mode 100644 dist/icons/controller/applet_single_joycon_left.png create mode 100644 dist/icons/controller/applet_single_joycon_left_dark.png create mode 100644 dist/icons/controller/applet_single_joycon_left_dark_disabled.png create mode 100644 dist/icons/controller/applet_single_joycon_left_disabled.png create mode 100644 dist/icons/controller/applet_single_joycon_left_midnight.png create mode 100644 dist/icons/controller/applet_single_joycon_left_midnight_disabled.png create mode 100644 dist/icons/controller/applet_single_joycon_right.png create mode 100644 dist/icons/controller/applet_single_joycon_right_dark.png create mode 100644 dist/icons/controller/applet_single_joycon_right_dark_disabled.png create mode 100644 dist/icons/controller/applet_single_joycon_right_disabled.png create mode 100644 dist/icons/controller/applet_single_joycon_right_midnight.png create mode 100644 dist/icons/controller/applet_single_joycon_right_midnight_disabled.png create mode 100644 src/core/frontend/applets/controller.cpp create mode 100644 src/core/frontend/applets/controller.h create mode 100644 src/core/hle/service/am/applets/controller.cpp create mode 100644 src/core/hle/service/am/applets/controller.h create mode 100644 src/yuzu/applets/controller.cpp create mode 100644 src/yuzu/applets/controller.h create mode 100644 src/yuzu/applets/controller.ui create mode 100644 src/yuzu/configuration/configure_input_dialog.cpp create mode 100644 src/yuzu/configuration/configure_input_dialog.h create mode 100644 src/yuzu/configuration/configure_input_dialog.ui diff --git a/dist/icons/controller/applet_dual_joycon.png b/dist/icons/controller/applet_dual_joycon.png new file mode 100644 index 0000000000000000000000000000000000000000..32e0a04ae5a6b8f656f4e5e3dcb571122a4d5b85 GIT binary patch literal 3554 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_dark.png b/dist/icons/controller/applet_dual_joycon_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6adc6635611eabfff010de0f541a1ada77f0d7a4 GIT binary patch literal 3554 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_dark_disabled.png b/dist/icons/controller/applet_dual_joycon_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..208603ee78b6d1f1afe941a0fc220b09b521c231 GIT binary patch literal 3527 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 Bv<m<L literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_disabled.png b/dist/icons/controller/applet_dual_joycon_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..43950618da140121d2ef0fe768c4f8c2eb7e4a48 GIT binary patch literal 3314 zcmV<O3=Q*%P)<h;3K|Lk000e1NJLTq002e+002e^1^@s6aW3M700004b3#c}2nYxW zd<bNS00009a7bBm000ie000ie0hKEb8vp<R8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H13~xz9K~#90?VNvbT-9~QKj-eslCZI5ewf%K4%AE`OreENC~ca- zNx&f_&=L|u5?l(QG1RuS63F^xNmsj)R<ccZCEJ-$+#&W52%4lrXA(+qQWuzX48(yz z;uwk(5^Tpnzy{mc`my))kGDcAdEf5a)yif%{?2%2@4M%od+yQw@xAArXE=$IIEfPo z0p^@&BR*(219%Mh6;OGCZJ9EJi)eEQI2*VS=mBanE3Liv6jO2dTv3_ptHH3RwW0Rc zK|1T|O3Y0eGcS9sxR6WjYi^G06A=w)e;`Pa?Jfbf0cF7Ff&Yxf`>s}8Ey&rx%Lu<y zWzeV78{MG!b8D;L&DDvu8)lgD;hh5i<0s?bEP=VGJ}2;SYUrIuthKJlalMaXW(>e{ zKnKtTYy>V&Id+G_L|Pjnd1pd2p18cMEHxd->6o&zp;J@vN^4W?;#_jpS`)53?J`w& zn<=k~cEl51?M<~06)>J-P^@FXTSkk3D}eiKOU)N2W5{HXo*~<SZFcpB4~0_0$!I)r zqYICG!`9coQH)Mglp$Toi({+%&joJ9`>S6M{6k;`A|Fi|Ssjh{eZx;A8c*DoGO`+x zj}~Ox%4Pdnn<6(M=0P(Oe!R2uo{tpLeiQ{eCKg+Ft}#568g|ox-!iuOZUD{;qC?O# zftwNitiOz03EZ5P1t1#lyCWL!bJ6a^U+3$it+8f6ovGDO=+UmO<XJ)N%0xkqvDO+F zPCaOZJ8etkh4kZZ0|UT}^zYE9d`CQaLo}XzuOps3(h*M{>F7$>LJb^6ES_ut*9R^o zMz+S{iG}&vw71l3MYTO;<lj`apo69$$Al_oEk*U&RgIA+Csf@5yas#%sDJ;da0qx^ zR9_d>*G<U0RY-rCQ1mS@M?{RMvR_q>1ZmgWT>I~W`(vypd21p41>StEwMJE{bx5;K z<Nd(bfgb@;;3vSd?TwKi0p}KCkRRHbYB$Du`%;?HUWDt~nj_B!ZJP>@tTZEKTL%UP zHZ5MfI2EK#V8@tgr+re;?bb#P_=s%5+h>*on}ELr0{vBkqP3yse&GJmvb8oGs;K(9 zI_67=&8-cQm$J39_4RK=<H?`Dv}?~Nf&UKDSMa4|QrDpFkvvlMz>C1g@Q(8-g{i1o zqsq4snpN3m*R1iszlr?NVHO3}9oP!TIch@xn@8#>a0@U5tN^YFtiKp2S7rRJ3JsTC z<|pc$d0x<qf~pSe7!~=j3lHtdCDY&EU*6S~*auj=>&AV+=|TG_6snSW2plx3+x=wA zCG<LQreFO;1a^#p85}zOkY9bYyKm)z!)|xV$TKTe*bTrFz&t$XWQSvie~*&&Y7txA zzre0tdj^0G&8$PE9#{|Nik6nzKUizSvEIbTtiAV?%yxr=l}Dt2XuxEy1n;9G-giCF z49o%s=gh0xh{uYbXN(nZ9@j{92$4Vd=~O3s#OSz7Xe^%CtSPs|q{73|p5*Oqjkj(F z){fR&o2WA7=1FyWktsj&Mzp7YQCnm6FNzscQCEUvp)H`Z(uHyruIlK#=TdUAVl#s9 z89$9;+?AQ6EZXxg7b&tNT`WWCn%Ksa%gv1KBXdMkcNI6D5;?{oXN-HGR7u!;%dXz= z7};2{%5TMJ-uOu-Rdp%{fy{8iJ6RfxyRADlIdAW|P!h*D)onW7Xu!-iBf}4ng%vwE zph{lHWD3}(+E)ec26+c%7phAq%AYm6@*&^>;E2ML8V+|BJEoHCF&YZh8JAj!>Y0cb z08tK!;YqTvVs&z-gmh~F&1BA-)}}}TNQ{<k@49&=mAXfT`69Am>C&YS0!z~WCWVsB z7~8O7uQQ=bf$xF7rtpFacdTlRMAIT$fc1D0zfHEKW)~_gZrI)Hr&HD3i<q1jKjTvC z5pELr8&o&PdXwjuVmwfy83UekfE#m&tOOPU7XiI(O*PdWU45VO6BgsXrb-zeS@e@S zFJr-lNXY^q>$RXzl;~6ngiKbv9Jr>vrN)0bsaE7yssEZ$*jZ6F^u**$9K-Q+40y3( zONm%<TT^5`aG9un*$tL`yuQBv@Z?SOP_m^|0DAFa#S8JAGpSf{TT|p&AaDpGD314x z0q<DPj1}K<0&K{O6X6(uSApvE@AU+X6(3KObScHU6JJu1B`7;8%Z5As)_g}ic|&_s zWD{OkVlf^oz5)moT{|8q*^G(C`!7+Ehk@%58s8f!i}`jctm^FTzZ}0<@dp4Un=z_- zvB;RC#e0pLT^3GN=f2b1+xr&{4GqV{ii7sq-``(;;Lylrf}E8SerB6$U!BAd#-U_0 zMnh%`%G+a4<Y7NqK|cii$)HYK#ROJNz?g~cUU$KP!)_<WJfg_`MrFH=C;KKdhD=Oq z#@O!u3q|yN9dui5!-~DOr|*&w?pl{JJ4VS{w7dTb5v@Fb!Q3qqBlM#0zhxZ9ih*iX z+2faTFfcF>+WE@f9{}fMUZ+CHEgkX1Gwn^a-<iy~U=ogr_VnF^x(9%el*{kCyVqaZ z+_?Prc(E4%I=cJ5iMTrz*tu)(4}fcZ)Qz&@ZFsEsvC6XH&ZDVmH)xhwz4JG_FF<70 z#AFRcXBp(5fk`{2_@$)!PmH~FoIgxzT;SVU8y3;qGah}x_Vmrqq47DOoir=1udlCA z<tym184-riY{-7WbWl~$IDAmFV?@}MSq_=;WozvS9$!f};K0mjsW<beU5)qNXCAPz zt*K_asCG{j8aWz%o-A=LfkC{De9|b|F(DUj0yeAiGZ9_Bs<GzS<Q_3!27MglDN((? zuC6ZM3pj@tAKnlAJ#b5FbM5yg3R%1C5F(8r`!gONRbjUdmbDc-rc4yQ$22!De~Sz{ zuc{L3>FZBF+u_i!rUTKw84W&o4XDK<ed~dr0<TOIYHO<580}8JC_;^cj~Mjv>C<#= zUE{Kny!|X0kBRmsuNGu^y7*Tiq#L*?cPkn3sn`U34llm*ZG2d9TXW=T;H$-1oEs(M zF@l+uSys)v391v9>-5NE#VaRiUco4tV`fZC{Ri-Ly7;a!9J6VTgED9|XT<LSUaVO0 zSn<b8VP{a3%rSL!bw@5(Fz*wH`?G0sbbgi`nq#0Mr~1{!;)zc)#)`ufz=}(OlHRV< z%=TBM(I&Td^z=_}Z>&Cs702SqwTk?yV3Ms(EB>oA{f)<oipK<k!QEq5Z>VBItXPcM z4Z6tb$oFI2$uCSz-&2HWJh?R*Pre<EC*SVq?pvQ-N`K?m-ob8#)6K|GALCi^#@^1B z2Nbr8$V?~LDKLLZXUqa%HZWU+bB?JMD%6=A*$(;(l+#4y!t{etRy_CM!Grf{sO$!m z11Qr=Z8)6ZrDSrXvzk$2C_0+MH;xtCmCLSnq?UxHXvR$4TLO`9qRtzX@KiPt&Dp<X zt9uQI;GIzGZ1Y{0kcAbO0#oLgw#J&N%E8-=7c0)miYJTUOG$7r^g%hr3TDMKXYLox zb|v>g@EknHn^YV<BfsQvLBXu}-J#H#s09AtqL3N0Ck(j)O~)HsfOU+q;!|=7hvCB_ zx;Lou0+xn8rRtv=c7m{yvAp8mT!Ie$v{U(MP{n~A<3iIORn#wLTM$&`WmNM=+Jv8| z%F8(<1{q_;%gB*etWK`gQ0Rw2Z4B%fyK>n+gx|&Di7SJ)@rS-ENZx5Js{26m<<Vub z;`_<+b+7lPf;ZDSJb_T)DdVgn(Sd=1P=49mP~?}W|AkY99Uw0TR5!|s=K$lfv{Y3P zjXCEE3V9afn6}2c7ZtYsde@%TLfU0RNZmti&5?Hhz9OB*>1MzB32=CMccZc5e;lpo z>`6AN@=M!N`}0EVE0~w);DJU#7R9;~Un;ci6k|6YD+cehI?vkvnX2-s*)ycJP-O)> z##%e%%0{kpB6mgOiQDtHIIh?XJWM*T*xKsDtxYwbt*)-l`k8Ys6!)>{-FN>{2g|NP z<nz&<<oApHAajb4$%-3_{ZPH=yOOob4guG9^z{9mhC+`<yOS@dao^RW<y+Hr#|=F_ zJ+lU>T!_=p>Cmi*wYS~|JdXEm_>X~S3e{Qcm-^#itu^6_s=sy0S46m6l}fzq1wTxy z3=dzBFPU)7nl;mpjFi0u*}wS5+jLb`_Je!c2#=h<;Or;TA=W;;tgW4RU)#M`_{jc$ wq7pCu{21PU)Nv9Sm_vzH#YvpRNfd?u0|N4n&yDZd8vp<R07*qoM6N<$f?BbEmjD0& literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_midnight.png b/dist/icons/controller/applet_dual_joycon_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..c7edea8a3529455236af85e4c4c067f8354fa671 GIT binary patch literal 3549 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 X-aoX9as?)~00000NkvXXu0mjfPXNo{ literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_midnight_disabled.png b/dist/icons/controller/applet_dual_joycon_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..ee1aafc858fc7ebaa8cdc4ac2ddc2d0aaa336241 GIT binary patch literal 3584 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 GLSTXfzq6JA literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld.png b/dist/icons/controller/applet_handheld.png new file mode 100644 index 0000000000000000000000000000000000000000..7f8dd22275b8dc5a56128f43a458a4e1ff9f58fd GIT binary patch literal 1671 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 RQ-}Zn002ovPDHLkV1fh28wUUY literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_dark.png b/dist/icons/controller/applet_handheld_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..41f6d7aea9325471576ca2bd20c3ba6d5d8d6dc6 GIT binary patch literal 1637 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_dark_disabled.png b/dist/icons/controller/applet_handheld_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..5a136ddd1db837d301bc247a84781cfd0c68e7a5 GIT binary patch literal 2642 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 AiU0rr literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_disabled.png b/dist/icons/controller/applet_handheld_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..53f8ce7adca22e1106566cc3efe84eda86b0815a GIT binary patch literal 2221 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_midnight.png b/dist/icons/controller/applet_handheld_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..7188b915456066975b1c2e401645d9fa7cee6f77 GIT binary patch literal 1644 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_midnight_disabled.png b/dist/icons/controller/applet_handheld_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..6ccfc325365322b407d32606ffcf7a8c55610add GIT binary patch literal 2634 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller.png b/dist/icons/controller/applet_pro_controller.png new file mode 100644 index 0000000000000000000000000000000000000000..e322588558e9603e9303ffcdcf4f5141b6cfd9e1 GIT binary patch literal 4382 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 L8=>_<!y)c}4U$#7 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_dark.png b/dist/icons/controller/applet_pro_controller_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c122b7b11e1e2b89905eee54f8bf829332d9431d GIT binary patch literal 4236 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_dark_disabled.png b/dist/icons/controller/applet_pro_controller_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..416e1e2fb0d8c62d9fc767df45b0aa899d6869c5 GIT binary patch literal 4477 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 P00000NkvXXu0mjfJ-OS{ literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_disabled.png b/dist/icons/controller/applet_pro_controller_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..72a549ea98d988c7e10d82bd85126f163b409ed1 GIT binary patch literal 4173 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 Xf-zkcF-jto00000NkvXXu0mjf@=_E? literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_midnight.png b/dist/icons/controller/applet_pro_controller_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..622e57fa133d305e09fd7c6b211000e3df712d0e GIT binary patch literal 4376 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 SbOYG{0000<MNUMnLSTYy8B4SP literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_midnight_disabled.png b/dist/icons/controller/applet_pro_controller_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2907f3be42b56b014388582d0bffca97e7e3a4df GIT binary patch literal 4459 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left.png b/dist/icons/controller/applet_single_joycon_left.png new file mode 100644 index 0000000000000000000000000000000000000000..47c44d43a7669f191262909b56f2a71d57ecf825 GIT binary patch literal 2083 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 N002ovPDHLkV1m#O{^kGx literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_dark.png b/dist/icons/controller/applet_single_joycon_left_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..69530b69cedce36db75cfea0c1e08132a3dc38d5 GIT binary patch literal 2067 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_dark_disabled.png b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe4b1475c1c4a8285b09b44a3605b5d1373a240 GIT binary patch literal 2520 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_disabled.png b/dist/icons/controller/applet_single_joycon_left_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c0102dc20cdd77146c52493b7919918418f0536f GIT binary patch literal 2179 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 FV1h7dE|mZP literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_midnight.png b/dist/icons/controller/applet_single_joycon_left_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..56522bccb3b8b7739113310b03c748cb0ffe2e84 GIT binary patch literal 2065 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..62434c188db9facb12b140e4743c1cede52f6160 GIT binary patch literal 2529 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right.png b/dist/icons/controller/applet_single_joycon_right.png new file mode 100644 index 0000000000000000000000000000000000000000..b0d4fba906d494d19e65db36317300959b20508d GIT binary patch literal 2150 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_dark.png b/dist/icons/controller/applet_single_joycon_right_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..af26cce4bb2cf476ac9b68cbda6b4bf494dd90e6 GIT binary patch literal 2146 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 YZ<i4O+o1JBmH+?%07*qoM6N<$g2q4p*#H0l literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_dark_disabled.png b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b33efab42a06765f9f34eba503c8d3ac9fc5651d GIT binary patch literal 2556 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 SP=1^M0000<MNUMnLSTZ?=+3+V literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_disabled.png b/dist/icons/controller/applet_single_joycon_right_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..01ca383226b53a43be3478fd2459541dce26f4a4 GIT binary patch literal 2212 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_midnight.png b/dist/icons/controller/applet_single_joycon_right_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf70e21e1d6cb6b92858b5a5da818d1a6499d40 GIT binary patch literal 2150 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e6693b0270aec23df0150b4209f02db5db06c83a GIT binary patch literal 2611 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 V3^!bd?ri`7002ovPDHLkV1mN{?QsAA literal 0 HcmV?d00001 diff --git a/dist/icons/controller/controller.qrc b/dist/icons/controller/controller.qrc index f44725d8ff..76083a9ac1 100644 --- a/dist/icons/controller/controller.qrc +++ b/dist/icons/controller/controller.qrc @@ -21,5 +21,35 @@ <file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file> <file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file> <file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file> + <file alias="applet_dual_joycon">applet_dual_joycon.png</file> + <file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file> + <file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file> + <file alias="applet_handheld">applet_handheld.png</file> + <file alias="applet_handheld_dark">applet_handheld_dark.png</file> + <file alias="applet_handheld_midnight">applet_handheld_midnight.png</file> + <file alias="applet_pro_controller">applet_pro_controller.png</file> + <file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file> + <file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file> + <file alias="applet_joycon_left">applet_single_joycon_left.png</file> + <file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file> + <file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file> + <file alias="applet_joycon_right">applet_single_joycon_right.png</file> + <file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file> + <file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file> + <file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file> + <file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file> + <file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file> + <file alias="applet_handheld_disabled">applet_handheld_disabled.png</file> + <file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file> + <file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file> + <file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file> + <file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file> + <file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file> + <file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file> + <file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file> + <file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file> + <file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file> + <file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file> + <file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file> </qresource> </RCC> diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 5da56520b0..5b05b436b2 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -41,6 +41,99 @@ QPushButton#buttonRefreshDevices { max-height: 20px; } +QWidget#bottomPerGameInput, +QWidget#topControllerApplet, +QWidget#bottomControllerApplet, +QGroupBox#groupPlayer1Connected:checked, +QGroupBox#groupPlayer2Connected:checked, +QGroupBox#groupPlayer3Connected:checked, +QGroupBox#groupPlayer4Connected:checked, +QGroupBox#groupPlayer5Connected:checked, +QGroupBox#groupPlayer6Connected:checked, +QGroupBox#groupPlayer7Connected:checked, +QGroupBox#groupPlayer8Connected:checked { + background-color: #f5f5f5; +} + +QWidget#topControllerApplet { + border-bottom: 1px solid #828790 +} + +QWidget#bottomPerGameInput, +QWidget#bottomControllerApplet { + border-top: 1px solid #828790 +} + +QWidget#topPerGameInput, +QWidget#middleControllerApplet { + background-color: #fff; +} + +QWidget#topPerGameInput QComboBox, +QWidget#middleControllerApplet QComboBox { + width: 123px; +} + +QWidget#connectedControllers { + background: transparent; +} + +QWidget#playersSupported, +QWidget#controllersSupported, +QWidget#controllerSupported1, +QWidget#controllerSupported2, +QWidget#controllerSupported3, +QWidget#controllerSupported4, +QWidget#controllerSupported5, +QWidget#controllerSupported6 { + border: none; + background: transparent; +} + +QGroupBox#groupPlayer1Connected, +QGroupBox#groupPlayer2Connected, +QGroupBox#groupPlayer3Connected, +QGroupBox#groupPlayer4Connected, +QGroupBox#groupPlayer5Connected, +QGroupBox#groupPlayer6Connected, +QGroupBox#groupPlayer7Connected, +QGroupBox#groupPlayer8Connected { + border: 1px solid #828790; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; +} + +QGroupBox#groupPlayer1Connected:unchecked, +QGroupBox#groupPlayer2Connected:unchecked, +QGroupBox#groupPlayer3Connected:unchecked, +QGroupBox#groupPlayer4Connected:unchecked, +QGroupBox#groupPlayer5Connected:unchecked, +QGroupBox#groupPlayer6Connected:unchecked, +QGroupBox#groupPlayer7Connected:unchecked, +QGroupBox#groupPlayer8Connected:unchecked { + border: 1px solid #d9d9d9; +} + +QGroupBox#groupPlayer1Connected::title, +QGroupBox#groupPlayer2Connected::title, +QGroupBox#groupPlayer3Connected::title, +QGroupBox#groupPlayer4Connected::title, +QGroupBox#groupPlayer5Connected::title, +QGroupBox#groupPlayer6Connected::title, +QGroupBox#groupPlayer7Connected::title, +QGroupBox#groupPlayer8Connected::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: 0px; + margin-right: -4px; + margin-bottom: 4px; +} + QCheckBox#checkboxPlayer1Connected, QCheckBox#checkboxPlayer2Connected, QCheckBox#checkboxPlayer3Connected, @@ -49,7 +142,43 @@ QCheckBox#checkboxPlayer5Connected, QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { - spacing: 0px; + spacing: 0px; +} + +QWidget#Player1LEDs QCheckBox, +QWidget#Player2LEDs QCheckBox, +QWidget#Player3LEDs QCheckBox, +QWidget#Player4LEDs QCheckBox, +QWidget#Player5LEDs QCheckBox, +QWidget#Player6LEDs QCheckBox, +QWidget#Player7LEDs QCheckBox, +QWidget#Player8LEDs QCheckBox { + spacing: 0px; +} + +QWidget#Player1LEDs QCheckBox::indicator, +QWidget#Player2LEDs QCheckBox::indicator, +QWidget#Player3LEDs QCheckBox::indicator, +QWidget#Player4LEDs QCheckBox::indicator, +QWidget#Player5LEDs QCheckBox::indicator, +QWidget#Player6LEDs QCheckBox::indicator, +QWidget#Player7LEDs QCheckBox::indicator, +QWidget#Player8LEDs QCheckBox::indicator { + width: 6px; + height: 6px; + margin-left: 0px; +} + +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { + width: 12px; + height: 12px; } QCheckBox#checkboxPlayer1Connected::indicator, @@ -60,10 +189,38 @@ QCheckBox#checkboxPlayer5Connected::indicator, QCheckBox#checkboxPlayer6Connected::indicator, QCheckBox#checkboxPlayer7Connected::indicator, QCheckBox#checkboxPlayer8Connected::indicator { - width: 14px; - height: 14px; + width: 14px; + height: 14px; } +QGroupBox#groupPlayer1Connected::indicator, +QGroupBox#groupPlayer2Connected::indicator, +QGroupBox#groupPlayer3Connected::indicator, +QGroupBox#groupPlayer4Connected::indicator, +QGroupBox#groupPlayer5Connected::indicator, +QGroupBox#groupPlayer6Connected::indicator, +QGroupBox#groupPlayer7Connected::indicator, +QGroupBox#groupPlayer8Connected::indicator { + width: 16px; + height: 16px; +} + +QWidget#Player1LEDs QCheckBox::indicator:checked, +QWidget#Player2LEDs QCheckBox::indicator:checked, +QWidget#Player3LEDs QCheckBox::indicator:checked, +QWidget#Player4LEDs QCheckBox::indicator:checked, +QWidget#Player5LEDs QCheckBox::indicator:checked, +QWidget#Player6LEDs QCheckBox::indicator:checked, +QWidget#Player7LEDs QCheckBox::indicator:checked, +QWidget#Player8LEDs QCheckBox::indicator:checked, +QGroupBox#groupPlayer1Connected::indicator:checked, +QGroupBox#groupPlayer2Connected::indicator:checked, +QGroupBox#groupPlayer3Connected::indicator:checked, +QGroupBox#groupPlayer4Connected::indicator:checked, +QGroupBox#groupPlayer5Connected::indicator:checked, +QGroupBox#groupPlayer6Connected::indicator:checked, +QGroupBox#groupPlayer7Connected::indicator:checked, +QGroupBox#groupPlayer8Connected::indicator:checked, QCheckBox#checkboxPlayer1Connected::indicator:checked, QCheckBox#checkboxPlayer2Connected::indicator:checked, QCheckBox#checkboxPlayer3Connected::indicator:checked, @@ -73,12 +230,28 @@ QCheckBox#checkboxPlayer6Connected::indicator:checked, QCheckBox#checkboxPlayer7Connected::indicator:checked, QCheckBox#checkboxPlayer8Connected::indicator:checked, QGroupBox#groupConnectedController::indicator:checked { - border-radius: 2px; - border: 1px solid black; - background: #39ff14; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: #39ff14; + image: none; } +QWidget#Player1LEDs QCheckBox::indicator:unchecked, +QWidget#Player2LEDs QCheckBox::indicator:unchecked, +QWidget#Player3LEDs QCheckBox::indicator:unchecked, +QWidget#Player4LEDs QCheckBox::indicator:unchecked, +QWidget#Player5LEDs QCheckBox::indicator:unchecked, +QWidget#Player6LEDs QCheckBox::indicator:unchecked, +QWidget#Player7LEDs QCheckBox::indicator:unchecked, +QWidget#Player8LEDs QCheckBox::indicator:unchecked, +QGroupBox#groupPlayer1Connected::indicator:unchecked, +QGroupBox#groupPlayer2Connected::indicator:unchecked, +QGroupBox#groupPlayer3Connected::indicator:unchecked, +QGroupBox#groupPlayer4Connected::indicator:unchecked, +QGroupBox#groupPlayer5Connected::indicator:unchecked, +QGroupBox#groupPlayer6Connected::indicator:unchecked, +QGroupBox#groupPlayer7Connected::indicator:unchecked, +QGroupBox#groupPlayer8Connected::indicator:unchecked, QCheckBox#checkboxPlayer1Connected::indicator:unchecked, QCheckBox#checkboxPlayer2Connected::indicator:unchecked, QCheckBox#checkboxPlayer3Connected::indicator:unchecked, @@ -88,8 +261,19 @@ QCheckBox#checkboxPlayer6Connected::indicator:unchecked, QCheckBox#checkboxPlayer7Connected::indicator:unchecked, QCheckBox#checkboxPlayer8Connected::indicator:unchecked, QGroupBox#groupConnectedController::indicator:unchecked { - border-radius: 2px; - border: 1px solid black; - background: transparent; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: transparent; + image: none; +} + +QWidget#controllerPlayer1, +QWidget#controllerPlayer2, +QWidget#controllerPlayer3, +QWidget#controllerPlayer4, +QWidget#controllerPlayer5, +QWidget#controllerPlayer6, +QWidget#controllerPlayer7, +QWidget#controllerPlayer8 { + background: transparent; } diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 16218f0c22..537558c1b6 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1284,59 +1284,6 @@ QPushButton#buttonRefreshDevices { padding: 0px 0px; } -QCheckBox#checkboxPlayer1Connected, -QCheckBox#checkboxPlayer2Connected, -QCheckBox#checkboxPlayer3Connected, -QCheckBox#checkboxPlayer4Connected, -QCheckBox#checkboxPlayer5Connected, -QCheckBox#checkboxPlayer6Connected, -QCheckBox#checkboxPlayer7Connected, -QCheckBox#checkboxPlayer8Connected { - spacing: 0px; -} - -QCheckBox#checkboxPlayer1Connected::indicator, -QCheckBox#checkboxPlayer2Connected::indicator, -QCheckBox#checkboxPlayer3Connected::indicator, -QCheckBox#checkboxPlayer4Connected::indicator, -QCheckBox#checkboxPlayer5Connected::indicator, -QCheckBox#checkboxPlayer6Connected::indicator, -QCheckBox#checkboxPlayer7Connected::indicator, -QCheckBox#checkboxPlayer8Connected::indicator { - width: 14px; - height: 14px; -} - -QCheckBox#checkboxPlayer1Connected::indicator:checked, -QCheckBox#checkboxPlayer2Connected::indicator:checked, -QCheckBox#checkboxPlayer3Connected::indicator:checked, -QCheckBox#checkboxPlayer4Connected::indicator:checked, -QCheckBox#checkboxPlayer5Connected::indicator:checked, -QCheckBox#checkboxPlayer6Connected::indicator:checked, -QCheckBox#checkboxPlayer7Connected::indicator:checked, -QCheckBox#checkboxPlayer8Connected::indicator:checked, -QGroupBox#groupConnectedController::indicator:checked { - border-radius: 2px; - border: 1px solid #929192; - background: #39ff14; - image: none; -} - -QCheckBox#checkboxPlayer1Connected::indicator:unchecked, -QCheckBox#checkboxPlayer2Connected::indicator:unchecked, -QCheckBox#checkboxPlayer3Connected::indicator:unchecked, -QCheckBox#checkboxPlayer4Connected::indicator:unchecked, -QCheckBox#checkboxPlayer5Connected::indicator:unchecked, -QCheckBox#checkboxPlayer6Connected::indicator:unchecked, -QCheckBox#checkboxPlayer7Connected::indicator:unchecked, -QCheckBox#checkboxPlayer8Connected::indicator:unchecked, -QGroupBox#groupConnectedController::indicator:unchecked { - border-radius: 2px; - border: 1px solid #929192; - background: transparent; - image: none; -} - QSpinBox#spinboxLStickRange, QSpinBox#spinboxRStickRange { padding: 4px 0px 5px 0px; @@ -1372,6 +1319,257 @@ QGroupBox#vibrationGroup::title { padding-right: 1px; } +QWidget#bottomPerGameInput, +QWidget#topControllerApplet, +QWidget#bottomControllerApplet, +QGroupBox#groupPlayer1Connected:checked, +QGroupBox#groupPlayer2Connected:checked, +QGroupBox#groupPlayer3Connected:checked, +QGroupBox#groupPlayer4Connected:checked, +QGroupBox#groupPlayer5Connected:checked, +QGroupBox#groupPlayer6Connected:checked, +QGroupBox#groupPlayer7Connected:checked, +QGroupBox#groupPlayer8Connected:checked { + background-color: #232629; +} + +QWidget#topPerGameInput, +QWidget#middleControllerApplet { + background-color: #31363b; +} + +QWidget#topPerGameInput QComboBox, +QWidget#middleControllerApplet QComboBox { + width: 119px; +} + +QRadioButton#radioDocked { + margin-left: -3px; +} + + +QRadioButton#radioUndocked { + margin-right: 5px; +} + +QWidget#connectedControllers { + background: transparent; +} + +QWidget#playersSupported, +QWidget#controllersSupported, +QWidget#controllerSupported1, +QWidget#controllerSupported2, +QWidget#controllerSupported3, +QWidget#controllerSupported4, +QWidget#controllerSupported5, +QWidget#controllerSupported6 { + border: none; + background: transparent; +} + +QGroupBox#groupPlayer1Connected, +QGroupBox#groupPlayer2Connected, +QGroupBox#groupPlayer3Connected, +QGroupBox#groupPlayer4Connected, +QGroupBox#groupPlayer5Connected, +QGroupBox#groupPlayer6Connected, +QGroupBox#groupPlayer7Connected, +QGroupBox#groupPlayer8Connected { + border: 1px solid #76797c; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; + margin-top: 0px; +} + +QGroupBox#groupPlayer1Connected:unchecked, +QGroupBox#groupPlayer2Connected:unchecked, +QGroupBox#groupPlayer3Connected:unchecked, +QGroupBox#groupPlayer4Connected:unchecked, +QGroupBox#groupPlayer5Connected:unchecked, +QGroupBox#groupPlayer6Connected:unchecked, +QGroupBox#groupPlayer7Connected:unchecked, +QGroupBox#groupPlayer8Connected:unchecked { + border: 1px solid #54575b; +} + +QGroupBox#groupPlayer1Connected::title, +QGroupBox#groupPlayer2Connected::title, +QGroupBox#groupPlayer3Connected::title, +QGroupBox#groupPlayer4Connected::title, +QGroupBox#groupPlayer5Connected::title, +QGroupBox#groupPlayer6Connected::title, +QGroupBox#groupPlayer7Connected::title, +QGroupBox#groupPlayer8Connected::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: -2px; + margin-right: -4px; + margin-bottom: 6px; +} + +QCheckBox#checkboxPlayer1Connected, +QCheckBox#checkboxPlayer2Connected, +QCheckBox#checkboxPlayer3Connected, +QCheckBox#checkboxPlayer4Connected, +QCheckBox#checkboxPlayer5Connected, +QCheckBox#checkboxPlayer6Connected, +QCheckBox#checkboxPlayer7Connected, +QCheckBox#checkboxPlayer8Connected { + spacing: 0px; +} + +QWidget#Player1LEDs, +QWidget#Player2LEDs, +QWidget#Player3LEDs, +QWidget#Player4LEDs, +QWidget#Player5LEDs, +QWidget#Player6LEDs, +QWidget#Player7LEDs, +QWidget#Player8LEDs { + background: transparent; +} + +QWidget#Player1LEDs QCheckBox, +QWidget#Player2LEDs QCheckBox, +QWidget#Player3LEDs QCheckBox, +QWidget#Player4LEDs QCheckBox, +QWidget#Player5LEDs QCheckBox, +QWidget#Player6LEDs QCheckBox, +QWidget#Player7LEDs QCheckBox, +QWidget#Player8LEDs QCheckBox { + spacing: 0px; + margin-bottom: 0px; + margin-right: 0px; +} + +QWidget#Player1LEDs QCheckBox::indicator, +QWidget#Player2LEDs QCheckBox::indicator, +QWidget#Player3LEDs QCheckBox::indicator, +QWidget#Player4LEDs QCheckBox::indicator, +QWidget#Player5LEDs QCheckBox::indicator, +QWidget#Player6LEDs QCheckBox::indicator, +QWidget#Player7LEDs QCheckBox::indicator, +QWidget#Player8LEDs QCheckBox::indicator { + width: 6px; + height: 6px; + margin-left: 0px; +} + +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { + width: 12px; + height: 12px; +} + +QCheckBox#checkboxPlayer1Connected::indicator, +QCheckBox#checkboxPlayer2Connected::indicator, +QCheckBox#checkboxPlayer3Connected::indicator, +QCheckBox#checkboxPlayer4Connected::indicator, +QCheckBox#checkboxPlayer5Connected::indicator, +QCheckBox#checkboxPlayer6Connected::indicator, +QCheckBox#checkboxPlayer7Connected::indicator, +QCheckBox#checkboxPlayer8Connected::indicator { + width: 14px; + height: 14px; +} + +QGroupBox#groupPlayer1Connected::indicator, +QGroupBox#groupPlayer2Connected::indicator, +QGroupBox#groupPlayer3Connected::indicator, +QGroupBox#groupPlayer4Connected::indicator, +QGroupBox#groupPlayer5Connected::indicator, +QGroupBox#groupPlayer6Connected::indicator, +QGroupBox#groupPlayer7Connected::indicator, +QGroupBox#groupPlayer8Connected::indicator { + width: 16px; + height: 16px; +} + +QWidget#Player1LEDs QCheckBox::indicator:checked, +QWidget#Player2LEDs QCheckBox::indicator:checked, +QWidget#Player3LEDs QCheckBox::indicator:checked, +QWidget#Player4LEDs QCheckBox::indicator:checked, +QWidget#Player5LEDs QCheckBox::indicator:checked, +QWidget#Player6LEDs QCheckBox::indicator:checked, +QWidget#Player7LEDs QCheckBox::indicator:checked, +QWidget#Player8LEDs QCheckBox::indicator:checked, +QGroupBox#groupPlayer1Connected::indicator:checked, +QGroupBox#groupPlayer2Connected::indicator:checked, +QGroupBox#groupPlayer3Connected::indicator:checked, +QGroupBox#groupPlayer4Connected::indicator:checked, +QGroupBox#groupPlayer5Connected::indicator:checked, +QGroupBox#groupPlayer6Connected::indicator:checked, +QGroupBox#groupPlayer7Connected::indicator:checked, +QGroupBox#groupPlayer8Connected::indicator:checked, +QCheckBox#checkboxPlayer1Connected::indicator:checked, +QCheckBox#checkboxPlayer2Connected::indicator:checked, +QCheckBox#checkboxPlayer3Connected::indicator:checked, +QCheckBox#checkboxPlayer4Connected::indicator:checked, +QCheckBox#checkboxPlayer5Connected::indicator:checked, +QCheckBox#checkboxPlayer6Connected::indicator:checked, +QCheckBox#checkboxPlayer7Connected::indicator:checked, +QCheckBox#checkboxPlayer8Connected::indicator:checked, +QGroupBox#groupConnectedController::indicator:checked { + border-radius: 2px; + border: 1px solid #929192; + background: #39ff14; + image: none; +} + +QWidget#Player1LEDs QCheckBox::indicator:unchecked, +QWidget#Player2LEDs QCheckBox::indicator:unchecked, +QWidget#Player3LEDs QCheckBox::indicator:unchecked, +QWidget#Player4LEDs QCheckBox::indicator:unchecked, +QWidget#Player5LEDs QCheckBox::indicator:unchecked, +QWidget#Player6LEDs QCheckBox::indicator:unchecked, +QWidget#Player7LEDs QCheckBox::indicator:unchecked, +QWidget#Player8LEDs QCheckBox::indicator:unchecked, +QGroupBox#groupPlayer1Connected::indicator:unchecked, +QGroupBox#groupPlayer2Connected::indicator:unchecked, +QGroupBox#groupPlayer3Connected::indicator:unchecked, +QGroupBox#groupPlayer4Connected::indicator:unchecked, +QGroupBox#groupPlayer5Connected::indicator:unchecked, +QGroupBox#groupPlayer6Connected::indicator:unchecked, +QGroupBox#groupPlayer7Connected::indicator:unchecked, +QGroupBox#groupPlayer8Connected::indicator:unchecked, +QCheckBox#checkboxPlayer1Connected::indicator:unchecked, +QCheckBox#checkboxPlayer2Connected::indicator:unchecked, +QCheckBox#checkboxPlayer3Connected::indicator:unchecked, +QCheckBox#checkboxPlayer4Connected::indicator:unchecked, +QCheckBox#checkboxPlayer5Connected::indicator:unchecked, +QCheckBox#checkboxPlayer6Connected::indicator:unchecked, +QCheckBox#checkboxPlayer7Connected::indicator:unchecked, +QCheckBox#checkboxPlayer8Connected::indicator:unchecked, +QGroupBox#groupConnectedController::indicator:unchecked { + border-radius: 2px; + border: 1px solid #929192; + background: transparent; + image: none; +} + +QWidget#controllerPlayer1, +QWidget#controllerPlayer2, +QWidget#controllerPlayer3, +QWidget#controllerPlayer4, +QWidget#controllerPlayer5, +QWidget#controllerPlayer6, +QWidget#controllerPlayer7, +QWidget#controllerPlayer8 { + background: transparent; +} + /* touchscreen mapping widget */ TouchScreenPreview { qproperty-dotHighlightColor: #3daee9; diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index a714e14759..9f6a0ff1df 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -2205,7 +2205,179 @@ QPushButton#buttonRefreshDevices { padding: 0px 0px; } +QSpinBox#spinboxLStickRange, +QSpinBox#spinboxRStickRange { + min-width: 38px; +} + +QGroupBox#motionGroup::indicator, +QGroupBox#vibrationGroup::indicator { + margin-left: 0px; +} + +QWidget#bottomPerGameInput QGroupBox#motionGroup, +QWidget#bottomPerGameInput QGroupBox#vibrationGroup, +QWidget#bottomPerGameInput QGroupBox#inputConfigGroup { + padding: 0px; +} + +QGroupBox#motionGroup::title, +QGroupBox#vibrationGroup::title { + spacing: 2px; + padding-left: 1px; + padding-right: 1px; +} + +QListWidget#selectorList { + background-color: #0f1922; +} + +QSpinBox, +QLineEdit, +QTreeView#hotkey_list, +QScrollArea#scrollArea QTreeView { + background-color: #0f1922; +} + +QWidget#bottomPerGameInput, +QWidget#topControllerApplet, +QWidget#bottomControllerApplet, +QGroupBox#groupPlayer1Connected:checked, +QGroupBox#groupPlayer2Connected:checked, +QGroupBox#groupPlayer3Connected:checked, +QGroupBox#groupPlayer4Connected:checked, +QGroupBox#groupPlayer5Connected:checked, +QGroupBox#groupPlayer6Connected:checked, +QGroupBox#groupPlayer7Connected:checked, +QGroupBox#groupPlayer8Connected:checked { + background-color: #0f1922; +} + +QWidget#topPerGameInput, +QWidget#middleControllerApplet { + background-color: #19232d; +} + +QWidget#topPerGameInput QComboBox, +QWidget#middleControllerApplet QComboBox { + padding-right: 2px; + width: 127px; +} + +QGroupBox#handheldGroup { + padding-left: 0px; +} + +QRadioButton#radioDocked { + margin-left: -1px; + padding-left: 0px; +} + +QRadioButton#radioDocked::indicator { + margin-left: 0px; +} + + +QRadioButton#radioUndocked { + margin-right: 2px; +} + +QWidget#connectedControllers { + background: transparent; +} + +QWidget#playersSupported, +QWidget#controllersSupported, +QWidget#controllerSupported1, +QWidget#controllerSupported2, +QWidget#controllerSupported3, +QWidget#controllerSupported4, +QWidget#controllerSupported5, +QWidget#controllerSupported6 { + border: none; + background: transparent; +} + +QGroupBox#groupPlayer1Connected, +QGroupBox#groupPlayer2Connected, +QGroupBox#groupPlayer3Connected, +QGroupBox#groupPlayer4Connected, +QGroupBox#groupPlayer5Connected, +QGroupBox#groupPlayer6Connected, +QGroupBox#groupPlayer7Connected, +QGroupBox#groupPlayer8Connected { + border: 1px solid #76797c; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; + margin-top: 0px; +} + + +QGroupBox#groupPlayer1Connected:unchecked, +QGroupBox#groupPlayer2Connected:unchecked, +QGroupBox#groupPlayer3Connected:unchecked, +QGroupBox#groupPlayer4Connected:unchecked, +QGroupBox#groupPlayer5Connected:unchecked, +QGroupBox#groupPlayer6Connected:unchecked, +QGroupBox#groupPlayer7Connected:unchecked, +QGroupBox#groupPlayer8Connected:unchecked { + border: 1px solid #32414b; +} + +QGroupBox#groupPlayer1Connected::title, +QGroupBox#groupPlayer2Connected::title, +QGroupBox#groupPlayer3Connected::title, +QGroupBox#groupPlayer4Connected::title, +QGroupBox#groupPlayer5Connected::title, +QGroupBox#groupPlayer6Connected::title, +QGroupBox#groupPlayer7Connected::title, +QGroupBox#groupPlayer8Connected::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: -2px; + margin-right: -4px; + margin-bottom: 6px; +} +QCheckBox#checkboxPlayer1Connected, +QCheckBox#checkboxPlayer2Connected, +QCheckBox#checkboxPlayer3Connected, +QCheckBox#checkboxPlayer4Connected, +QCheckBox#checkboxPlayer5Connected, +QCheckBox#checkboxPlayer6Connected, +QCheckBox#checkboxPlayer7Connected, +QCheckBox#checkboxPlayer8Connected { + spacing: 0px; +} + +QWidget#connectedControllers QLabel { + padding: 0px; +} + +QWidget#Player1LEDs, +QWidget#Player2LEDs, +QWidget#Player3LEDs, +QWidget#Player4LEDs, +QWidget#Player5LEDs, +QWidget#Player6LEDs, +QWidget#Player7LEDs, +QWidget#Player8LEDs { + background: transparent; +} + +QWidget#Player1LEDs QCheckBox, +QWidget#Player2LEDs QCheckBox, +QWidget#Player3LEDs QCheckBox, +QWidget#Player4LEDs QCheckBox, +QWidget#Player5LEDs QCheckBox, +QWidget#Player6LEDs QCheckBox, +QWidget#Player7LEDs QCheckBox, +QWidget#Player8LEDs QCheckBox, QCheckBox#checkboxPlayer1Connected, QCheckBox#checkboxPlayer2Connected, QCheckBox#checkboxPlayer3Connected, @@ -2215,6 +2387,34 @@ QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { spacing: 0px; + padding-top: 0px; + padding-bottom: 0px; + background: transparent; +} + +QWidget#Player1LEDs QCheckBox::indicator, +QWidget#Player2LEDs QCheckBox::indicator, +QWidget#Player3LEDs QCheckBox::indicator, +QWidget#Player4LEDs QCheckBox::indicator, +QWidget#Player5LEDs QCheckBox::indicator, +QWidget#Player6LEDs QCheckBox::indicator, +QWidget#Player7LEDs QCheckBox::indicator, +QWidget#Player8LEDs QCheckBox::indicator { + width: 6px; + height: 6px; + margin-left: 0px; +} + +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { + width: 12px; + height: 12px; } QCheckBox#checkboxPlayer1Connected::indicator, @@ -2227,8 +2427,25 @@ QCheckBox#checkboxPlayer7Connected::indicator, QCheckBox#checkboxPlayer8Connected::indicator { width: 14px; height: 14px; + margin-left: 2px; } +QWidget#Player1LEDs QCheckBox::indicator:checked, +QWidget#Player2LEDs QCheckBox::indicator:checked, +QWidget#Player3LEDs QCheckBox::indicator:checked, +QWidget#Player4LEDs QCheckBox::indicator:checked, +QWidget#Player5LEDs QCheckBox::indicator:checked, +QWidget#Player6LEDs QCheckBox::indicator:checked, +QWidget#Player7LEDs QCheckBox::indicator:checked, +QWidget#Player8LEDs QCheckBox::indicator:checked, +QGroupBox#groupPlayer1Connected::indicator:checked, +QGroupBox#groupPlayer2Connected::indicator:checked, +QGroupBox#groupPlayer3Connected::indicator:checked, +QGroupBox#groupPlayer4Connected::indicator:checked, +QGroupBox#groupPlayer5Connected::indicator:checked, +QGroupBox#groupPlayer6Connected::indicator:checked, +QGroupBox#groupPlayer7Connected::indicator:checked, +QGroupBox#groupPlayer8Connected::indicator:checked, QCheckBox#checkboxPlayer1Connected::indicator:checked, QCheckBox#checkboxPlayer2Connected::indicator:checked, QCheckBox#checkboxPlayer3Connected::indicator:checked, @@ -2244,6 +2461,22 @@ QGroupBox#groupConnectedController::indicator:checked { image: none; } +QWidget#Player1LEDs QCheckBox::indicator:unchecked, +QWidget#Player2LEDs QCheckBox::indicator:unchecked, +QWidget#Player3LEDs QCheckBox::indicator:unchecked, +QWidget#Player4LEDs QCheckBox::indicator:unchecked, +QWidget#Player5LEDs QCheckBox::indicator:unchecked, +QWidget#Player6LEDs QCheckBox::indicator:unchecked, +QWidget#Player7LEDs QCheckBox::indicator:unchecked, +QWidget#Player8LEDs QCheckBox::indicator:unchecked, +QGroupBox#groupPlayer1Connected::indicator:unchecked, +QGroupBox#groupPlayer2Connected::indicator:unchecked, +QGroupBox#groupPlayer3Connected::indicator:unchecked, +QGroupBox#groupPlayer4Connected::indicator:unchecked, +QGroupBox#groupPlayer5Connected::indicator:unchecked, +QGroupBox#groupPlayer6Connected::indicator:unchecked, +QGroupBox#groupPlayer7Connected::indicator:unchecked, +QGroupBox#groupPlayer8Connected::indicator:unchecked, QCheckBox#checkboxPlayer1Connected::indicator:unchecked, QCheckBox#checkboxPlayer2Connected::indicator:unchecked, QCheckBox#checkboxPlayer3Connected::indicator:unchecked, @@ -2255,34 +2488,17 @@ QCheckBox#checkboxPlayer8Connected::indicator:unchecked, QGroupBox#groupConnectedController::indicator:unchecked { border-radius: 2px; border: 1px solid #929192; - background: transparent; + background: #19232d; image: none; } -QSpinBox#spinboxLStickRange, -QSpinBox#spinboxRStickRange { - min-width: 38px; +QWidget#controllerPlayer1, +QWidget#controllerPlayer2, +QWidget#controllerPlayer3, +QWidget#controllerPlayer4, +QWidget#controllerPlayer5, +QWidget#controllerPlayer6, +QWidget#controllerPlayer7, +QWidget#controllerPlayer8 { + background: transparent; } - -QGroupBox#motionGroup::indicator, -QGroupBox#vibrationGroup::indicator { - margin-left: 0px; -} - -QGroupBox#motionGroup::title, -QGroupBox#vibrationGroup::title { -spacing: 2px; - padding-left: 1px; - padding-right: 1px; -} - -QListWidget#selectorList { - background-color: #0f1922; -} - -QSpinBox, -QLineEdit, -QTreeView#hotkey_list, -QScrollArea#scrollArea QTreeView { - background-color: #0f1922; -} \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c85c9485f9..a39940e4c9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -126,6 +126,8 @@ add_library(core STATIC file_sys/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h + frontend/applets/controller.cpp + frontend/applets/controller.h frontend/applets/error.cpp frontend/applets/error.h frontend/applets/general_frontend.cpp @@ -244,6 +246,8 @@ add_library(core STATIC hle/service/am/applet_oe.h hle/service/am/applets/applets.cpp hle/service/am/applets/applets.h + hle/service/am/applets/controller.cpp + hle/service/am/applets/controller.h hle/service/am/applets/error.cpp hle/service/am/applets/error.h hle/service/am/applets/general_backend.cpp diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp new file mode 100644 index 0000000000..0fbc7932ce --- /dev/null +++ b/src/core/frontend/applets/controller.cpp @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +namespace Core::Frontend { + +ControllerApplet::~ControllerApplet() = default; + +DefaultControllerApplet::~DefaultControllerApplet() = default; + +void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback, + ControllerParameters parameters) const { + LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!"); + + auto& npad = + Core::System::GetInstance() + .ServiceManager() + .GetService<Service::HID::Hid>("hid") + ->GetAppletResource() + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + + auto& players = Settings::values.players; + + // Deduce the best configuration based on the input parameters. + for (std::size_t index = 0; index < players.size(); ++index) { + // First, disconnect all controllers regardless of the value of keep_controllers_connected. + // This makes it easy to connect the desired controllers. + npad.DisconnectNPadAtIndex(index); + } + + callback(); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h new file mode 100644 index 0000000000..0908f2b696 --- /dev/null +++ b/src/core/frontend/applets/controller.h @@ -0,0 +1,45 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> + +#include "common/common_types.h" + +namespace Core::Frontend { + +using BorderColor = std::array<u8, 4>; + +struct ControllerParameters { + s8 min_players{}; + s8 max_players{}; + bool keep_controllers_connected{}; + bool enable_single_mode{}; + bool enable_border_color{}; + std::vector<BorderColor> border_colors{}; + bool allow_pro_controller{}; + bool allow_handheld{}; + bool allow_dual_joycons{}; + bool allow_left_joycon{}; + bool allow_right_joycon{}; +}; + +class ControllerApplet { +public: + virtual ~ControllerApplet(); + + virtual void ReconfigureControllers(std::function<void()> callback, + ControllerParameters parameters) const = 0; +}; + +class DefaultControllerApplet final : public ControllerApplet { +public: + ~DefaultControllerApplet() override; + + void ReconfigureControllers(std::function<void()> callback, + ControllerParameters parameters) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index c3261f3e60..4e0800f9aa 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -5,6 +5,7 @@ #include <cstring> #include "common/assert.h" #include "core/core.h" +#include "core/frontend/applets/controller.h" #include "core/frontend/applets/error.h" #include "core/frontend/applets/general_frontend.h" #include "core/frontend/applets/profile_select.h" @@ -15,6 +16,7 @@ #include "core/hle/kernel/writable_event.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/controller.h" #include "core/hle/service/am/applets/error.h" #include "core/hle/service/am/applets/general_backend.h" #include "core/hle/service/am/applets/profile_select.h" @@ -140,14 +142,14 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, +AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, + ErrorApplet error, ParentalControlsApplet parental_controls, PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser, - ECommerceApplet e_commerce) - : parental_controls{std::move(parental_controls)}, error{std::move(error)}, - photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)}, - software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)}, - e_commerce{std::move(e_commerce)} {} + SoftwareKeyboard software_keyboard, WebBrowser web_browser) + : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)}, + parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)}, + profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)}, + web_browser{std::move(web_browser)} {} AppletFrontendSet::~AppletFrontendSet() = default; @@ -164,20 +166,37 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const { } void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { - if (set.parental_controls != nullptr) - frontend.parental_controls = std::move(set.parental_controls); - if (set.error != nullptr) - frontend.error = std::move(set.error); - if (set.photo_viewer != nullptr) - frontend.photo_viewer = std::move(set.photo_viewer); - if (set.profile_select != nullptr) - frontend.profile_select = std::move(set.profile_select); - if (set.software_keyboard != nullptr) - frontend.software_keyboard = std::move(set.software_keyboard); - if (set.web_browser != nullptr) - frontend.web_browser = std::move(set.web_browser); - if (set.e_commerce != nullptr) + if (set.controller != nullptr) { + frontend.controller = std::move(set.controller); + } + + if (set.e_commerce != nullptr) { frontend.e_commerce = std::move(set.e_commerce); + } + + if (set.error != nullptr) { + frontend.error = std::move(set.error); + } + + if (set.parental_controls != nullptr) { + frontend.parental_controls = std::move(set.parental_controls); + } + + if (set.photo_viewer != nullptr) { + frontend.photo_viewer = std::move(set.photo_viewer); + } + + if (set.profile_select != nullptr) { + frontend.profile_select = std::move(set.profile_select); + } + + if (set.software_keyboard != nullptr) { + frontend.software_keyboard = std::move(set.software_keyboard); + } + + if (set.web_browser != nullptr) { + frontend.web_browser = std::move(set.web_browser); + } } void AppletManager::SetDefaultAppletFrontendSet() { @@ -186,15 +205,23 @@ void AppletManager::SetDefaultAppletFrontendSet() { } void AppletManager::SetDefaultAppletsIfMissing() { - if (frontend.parental_controls == nullptr) { - frontend.parental_controls = - std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); + if (frontend.controller == nullptr) { + frontend.controller = std::make_unique<Core::Frontend::DefaultControllerApplet>(); + } + + if (frontend.e_commerce == nullptr) { + frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>(); } if (frontend.error == nullptr) { frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>(); } + if (frontend.parental_controls == nullptr) { + frontend.parental_controls = + std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); + } + if (frontend.photo_viewer == nullptr) { frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>(); } @@ -211,10 +238,6 @@ void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.web_browser == nullptr) { frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); } - - if (frontend.e_commerce == nullptr) { - frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>(); - } } void AppletManager::ClearAll() { @@ -225,6 +248,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { switch (id) { case AppletId::Auth: return std::make_shared<Auth>(system, *frontend.parental_controls); + case AppletId::Controller: + return std::make_shared<Controller>(system, *frontend.controller); case AppletId::Error: return std::make_shared<Error>(system, *frontend.error); case AppletId::ProfileSelect: diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index e75be86a23..a1f4cf8970 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -17,6 +17,7 @@ class System; } namespace Core::Frontend { +class ControllerApplet; class ECommerceApplet; class ErrorApplet; class ParentalControlsApplet; @@ -155,19 +156,20 @@ protected: }; struct AppletFrontendSet { - using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; + using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>; + using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>; using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>; + using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>; using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>; using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>; using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>; - using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>; AppletFrontendSet(); - AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, - PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser, - ECommerceApplet e_commerce); + AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error, + ParentalControlsApplet parental_controls, PhotoViewer photo_viewer, + ProfileSelect profile_select, SoftwareKeyboard software_keyboard, + WebBrowser web_browser); ~AppletFrontendSet(); AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -176,13 +178,14 @@ struct AppletFrontendSet { AppletFrontendSet(AppletFrontendSet&&) noexcept; AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; - ParentalControlsApplet parental_controls; + ControllerApplet controller; + ECommerceApplet e_commerce; ErrorApplet error; + ParentalControlsApplet parental_controls; PhotoViewer photo_viewer; ProfileSelect profile_select; SoftwareKeyboard software_keyboard; WebBrowser web_browser; - ECommerceApplet e_commerce; }; class AppletManager { diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp new file mode 100644 index 0000000000..2a45a388ff --- /dev/null +++ b/src/core/hle/service/am/applets/controller.cpp @@ -0,0 +1,197 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/controller.h" +#include "core/hle/service/hid/controllers/npad.h" + +namespace Service::AM::Applets { + +// This error code (0x183ACA) is thrown when the applet fails to initialize. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101}; +// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102}; + +static Core::Frontend::ControllerParameters ConvertToFrontendParameters( + ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, + std::vector<IdentificationColor> identification_colors) { + HID::Controller_NPad::NPadType npad_style_set; + npad_style_set.raw = private_arg.style_set; + + return { + .min_players = header.player_count_min, + .max_players = header.player_count_max, + .keep_controllers_connected = header.enable_take_over_connection, + .enable_single_mode = header.enable_single_mode, + .enable_border_color = header.enable_identification_color, + .border_colors = identification_colors, + .allow_pro_controller = npad_style_set.pro_controller == 1, + .allow_handheld = npad_style_set.handheld == 1, + .allow_dual_joycons = npad_style_set.joycon_dual == 1, + .allow_left_joycon = npad_style_set.joycon_left == 1, + .allow_right_joycon = npad_style_set.joycon_right == 1, + }; +} + +Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} + +Controller::~Controller() = default; + +void Controller::Initialize() { + Applet::Initialize(); + + LOG_INFO(Service_HID, "Initializing Controller Applet."); + + LOG_DEBUG(Service_HID, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + library_applet_version = LibraryAppletVersion{common_args.library_version}; + + const auto private_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(private_arg_storage != nullptr); + + const auto& private_arg = private_arg_storage->GetData(); + ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); + + std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate)); + ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), + "Unknown ControllerSupportArgPrivate revision={} with size={}", + library_applet_version, controller_private_arg.arg_private_size); + + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto user_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(user_arg_storage != nullptr); + + const auto& user_arg = user_arg_storage->GetData(); + switch (library_applet_version) { + case LibraryAppletVersion::Version3: + case LibraryAppletVersion::Version4: + case LibraryAppletVersion::Version5: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld)); + break; + case LibraryAppletVersion::Version7: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", + library_applet_version, controller_private_arg.arg_size); + ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + } + break; + } + case ControllerSupportMode::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + default: { + UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); + break; + } + } +} + +bool Controller::TransactionComplete() const { + return complete; +} + +ResultCode Controller::GetStatus() const { + return status; +} + +void Controller::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void Controller::Execute() { + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto parameters = [this] { + switch (library_applet_version) { + case LibraryAppletVersion::Version3: + case LibraryAppletVersion::Version4: + case LibraryAppletVersion::Version5: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_old.header, + std::vector<IdentificationColor>( + controller_user_arg_old.identification_colors.begin(), + controller_user_arg_old.identification_colors.end())); + case LibraryAppletVersion::Version7: + default: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_new.header, + std::vector<IdentificationColor>( + controller_user_arg_new.identification_colors.begin(), + controller_user_arg_new.identification_colors.end())); + } + }(); + + is_single_mode = parameters.enable_single_mode; + + LOG_DEBUG( + Service_HID, + "Controller Parameters: min_players={}, max_players={}, keep_controllers_connected={}, " + "enable_single_mode={}, enable_border_color={}, allow_pro_controller={}, " + "allow_handheld={}, allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", + parameters.min_players, parameters.max_players, parameters.keep_controllers_connected, + parameters.enable_single_mode, parameters.enable_border_color, + parameters.allow_pro_controller, parameters.allow_handheld, + parameters.allow_dual_joycons, parameters.allow_left_joycon, + parameters.allow_right_joycon); + + frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters); + break; + } + case ControllerSupportMode::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + default: { + ConfigurationComplete(); + break; + } + } +} + +void Controller::ConfigurationComplete() { + ControllerSupportResultInfo result_info{}; + + const auto& players = Settings::values.players; + + // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. + // Otherwise, only count connected players from P1-P8. + result_info.player_count = + is_single_mode ? 1 + : static_cast<s8>(std::count_if( + players.begin(), players.end() - 2, + [](Settings::PlayerInput player) { return player.connected; })); + + result_info.selected_id = HID::Controller_NPad::IndexToNPad( + std::distance(players.begin(), + std::find_if(players.begin(), players.end(), + [](Settings::PlayerInput player) { return player.connected; }))); + + result_info.result = 0; + + LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}", + result_info.player_count, result_info.selected_id, result_info.result); + + complete = true; + out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo)); + std::memcpy(out_data.data(), &result_info, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out_data))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h new file mode 100644 index 0000000000..90a78d5082 --- /dev/null +++ b/src/core/hle/service/am/applets/controller.h @@ -0,0 +1,119 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +using IdentificationColor = std::array<u8, 4>; + +enum class LibraryAppletVersion : u32_le { + Version3 = 0x3, // 1.0.0 - 2.3.0 + Version4 = 0x4, // 3.0.0 - 5.1.0 + Version5 = 0x5, // 6.0.0 - 7.0.1 + Version7 = 0x7, // 8.0.0+ +}; + +enum class ControllerSupportMode : u8 { + ShowControllerSupport = 0, + ShowControllerStrapGuide = 1, + ShowControllerFirmwareUpdate = 2, +}; + +enum class ControllerSupportCaller : u8 { + Application = 0, + System = 1, +}; + +struct ControllerSupportArgPrivate { + u32 arg_private_size{}; + u32 arg_size{}; + bool flag_0{}; + bool flag_1{}; + ControllerSupportMode mode{}; + ControllerSupportCaller caller{}; + u32 style_set{}; + u32 joy_hold_type{}; +}; +static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, + "ControllerSupportArgPrivate has incorrect size."); + +struct ControllerSupportArgHeader { + s8 player_count_min{}; + s8 player_count_max{}; + bool enable_take_over_connection{}; + bool enable_left_justify{}; + bool enable_permit_joy_dual{}; + bool enable_single_mode{}; + bool enable_identification_color{}; +}; +static_assert(sizeof(ControllerSupportArgHeader) == 0x7, + "ControllerSupportArgHeader has incorrect size."); + +// LibraryAppletVersion 0x3, 0x4, 0x5 +struct ControllerSupportArgOld { + ControllerSupportArgHeader header{}; + std::array<IdentificationColor, 4> identification_colors{}; + bool enable_explain_text{}; + std::array<std::array<char, 0x81>, 4> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgOld) == 0x21C, + "ControllerSupportArgOld has incorrect size."); + +// LibraryAppletVersion 0x7 +struct ControllerSupportArgNew { + ControllerSupportArgHeader header{}; + std::array<IdentificationColor, 8> identification_colors{}; + bool enable_explain_text{}; + std::array<std::array<char, 0x81>, 8> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgNew) == 0x430, + "ControllerSupportArgNew has incorrect size."); + +struct ControllerSupportResultInfo { + s8 player_count{}; + INSERT_PADDING_BYTES(3); + u32 selected_id{}; + u32 result{}; +}; +static_assert(sizeof(ControllerSupportResultInfo) == 0xC, + "ControllerSupportResultInfo has incorrect size."); + +class Controller final : public Applet { +public: + explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_); + ~Controller() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void ConfigurationComplete(); + +private: + const Core::Frontend::ControllerApplet& frontend; + + LibraryAppletVersion library_applet_version; + ControllerSupportArgPrivate controller_private_arg; + ControllerSupportArgOld controller_user_arg_old; + ControllerSupportArgNew controller_user_arg_new; + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; + bool is_single_mode{false}; + std::vector<u8> out_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 45fde8df25..efb953d93c 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -193,7 +193,8 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { controller.battery_level[0] = BATTERY_FULL; controller.battery_level[1] = BATTERY_FULL; controller.battery_level[2] = BATTERY_FULL; - styleset_changed_events[controller_idx].writable->Signal(); + + SignalStyleSetChangedEvent(IndexToNPad(controller_idx)); } void Controller_NPad::OnInit() { @@ -518,13 +519,17 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, last_processed_vibration = vibrations.back(); } +Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { + return last_processed_vibration; +} + std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; return styleset_event.readable; } -Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { - return last_processed_vibration; +void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const { + styleset_changed_events[NPadIdToIndex(npad_id)].writable->Signal(); } void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { @@ -534,7 +539,7 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected) { if (!connected) { - DisconnectNPad(IndexToNPad(npad_index)); + DisconnectNPadAtIndex(npad_index); return; } @@ -554,16 +559,19 @@ void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::siz } void Controller_NPad::DisconnectNPad(u32 npad_id) { - const auto npad_index = NPadIdToIndex(npad_id); - connected_controllers[npad_index].is_connected = false; + DisconnectNPadAtIndex(NPadIdToIndex(npad_id)); +} + +void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) { Settings::values.players[npad_index].connected = false; + connected_controllers[npad_index].is_connected = false; auto& controller = shared_memory_entries[npad_index]; controller.joy_styles.raw = 0; // Zero out controller.device_type.raw = 0; controller.properties.raw = 0; - styleset_changed_events[npad_index].writable->Signal(); + SignalStyleSetChangedEvent(IndexToNPad(npad_index)); } void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { @@ -650,13 +658,13 @@ void Controller_NPad::ClearAllConnectedControllers() { } void Controller_NPad::DisconnectAllConnectedControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { controller.is_connected = false; } } void Controller_NPad::ConnectAllDisconnectedControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { if (controller.type != NPadControllerType::None && !controller.is_connected) { controller.is_connected = true; } @@ -664,7 +672,7 @@ void Controller_NPad::ConnectAllDisconnectedControllers() { } void Controller_NPad::ClearAllControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { controller.type = NPadControllerType::None; controller.is_connected = false; } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 75ce5b7313..40c7633768 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -115,15 +115,19 @@ public: void VibrateController(const std::vector<u32>& controller_ids, const std::vector<Vibration>& vibrations); - std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; Vibration GetLastVibration() const; + std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; + void SignalStyleSetChangedEvent(u32 npad_id) const; + // Adds a new controller at an index. void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index); // Adds a new controller at an index with connection status. void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected); void DisconnectNPad(u32 npad_id); + void DisconnectNPadAtIndex(std::size_t index); + void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; LedPattern GetLedPattern(u32 npad_id); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3ea4e56017..cc0291b15b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -9,6 +9,9 @@ add_executable(yuzu about_dialog.cpp about_dialog.h aboutdialog.ui + applets/controller.cpp + applets/controller.h + applets/controller.ui applets/error.cpp applets/error.h applets/profile_select.cpp @@ -62,12 +65,15 @@ add_executable(yuzu configuration/configure_input.cpp configuration/configure_input.h configuration/configure_input.ui - configuration/configure_input_player.cpp - configuration/configure_input_player.h - configuration/configure_input_player.ui configuration/configure_input_advanced.cpp configuration/configure_input_advanced.h configuration/configure_input_advanced.ui + configuration/configure_input_dialog.cpp + configuration/configure_input_dialog.h + configuration/configure_input_dialog.ui + configuration/configure_input_player.cpp + configuration/configure_input_player.h + configuration/configure_input_player.ui configuration/configure_motion_touch.cpp configuration/configure_motion_touch.h configuration/configure_motion_touch.ui diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp new file mode 100644 index 0000000000..8ccf61be0a --- /dev/null +++ b/src/yuzu/applets/controller.cpp @@ -0,0 +1,568 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "ui_controller.h" +#include "yuzu/applets/controller.h" +#include "yuzu/configuration/configure_input_dialog.h" +#include "yuzu/main.h" + +constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{ + {1, 0, 0, 0}, + {1, 1, 0, 0}, + {1, 1, 1, 0}, + {1, 1, 1, 1}, + {1, 0, 0, 1}, + {1, 0, 1, 0}, + {1, 0, 1, 1}, + {0, 1, 1, 0}, +}}; + +namespace { + +void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, + bool connected) { + Core::System& system{Core::System::GetInstance()}; + + if (!system.IsPoweredOn()) { + return; + } + + Service::SM::ServiceManager& sm = system.ServiceManager(); + + auto& npad = + sm.GetService<Service::HID::Hid>("hid") + ->GetAppletResource() + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + + npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); +} + +bool IsControllerCompatible(Settings::ControllerType controller_type, + Core::Frontend::ControllerParameters parameters) { + switch (controller_type) { + case Settings::ControllerType::ProController: + return parameters.allow_pro_controller; + case Settings::ControllerType::DualJoyconDetached: + return parameters.allow_dual_joycons; + case Settings::ControllerType::LeftJoycon: + return parameters.allow_left_joycon; + case Settings::ControllerType::RightJoycon: + return parameters.allow_right_joycon; + case Settings::ControllerType::Handheld: + return parameters.enable_single_mode && parameters.allow_handheld; + default: + return false; + } +} + +/// Maps the controller type combobox index to Controller Type enum +constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { + switch (index) { + case 0: + default: + return Settings::ControllerType::ProController; + case 1: + return Settings::ControllerType::DualJoyconDetached; + case 2: + return Settings::ControllerType::LeftJoycon; + case 3: + return Settings::ControllerType::RightJoycon; + case 4: + return Settings::ControllerType::Handheld; + } +} + +/// Maps the Controller Type enum to controller type combobox index +constexpr int GetIndexFromControllerType(Settings::ControllerType type) { + switch (type) { + case Settings::ControllerType::ProController: + default: + return 0; + case Settings::ControllerType::DualJoyconDetached: + return 1; + case Settings::ControllerType::LeftJoycon: + return 2; + case Settings::ControllerType::RightJoycon: + return 3; + case Settings::ControllerType::Handheld: + return 4; + } +} + +} // namespace + +QtControllerSelectorDialog::QtControllerSelectorDialog( + QWidget* parent, Core::Frontend::ControllerParameters parameters_, InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), + parameters(std::move(parameters_)), input_subsystem(input_subsystem_) { + ui->setupUi(this); + + player_widgets = { + ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, + ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, + }; + + player_groupboxes = { + ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, + ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, + ui->groupPlayer7Connected, ui->groupPlayer8Connected, + }; + + connected_controller_icons = { + ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, + ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, + }; + + led_patterns_boxes = {{ + {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, + ui->checkboxPlayer1LED4}, + {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, + ui->checkboxPlayer2LED4}, + {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, + ui->checkboxPlayer3LED4}, + {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, + ui->checkboxPlayer4LED4}, + {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, + ui->checkboxPlayer5LED4}, + {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, + ui->checkboxPlayer6LED4}, + {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, + ui->checkboxPlayer7LED4}, + {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, + ui->checkboxPlayer8LED4}, + }}; + + emulated_controllers = { + ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, + ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, + ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, + }; + + player_labels = { + ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, + ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, + }; + + connected_controller_labels = { + ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, + ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, + ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, + }; + + connected_controller_checkboxes = { + ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, + ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, + ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, + }; + + for (std::size_t i = 0; i < player_widgets.size(); ++i) { + connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { + if (checked) { + for (std::size_t index = 0; index <= i; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } else { + for (std::size_t index = i; index < player_widgets.size(); ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } + }); + + connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), + [this, i](int) { + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + CheckIfParametersMet(); + }); + + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + player_groupboxes[i]->setChecked(state == Qt::Checked); + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + CheckIfParametersMet(); + }); + + if (i == 0) { + connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), + [this](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index) == + Settings::ControllerType::Handheld); + }); + } + } + + connect(ui->inputConfigButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureInputDialog); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &QtControllerSelectorDialog::ApplyConfiguration); + + SetSupportedControllers(); + DisableUnsupportedPlayers(); + LoadConfiguration(); + + // If keep_controllers_connected is false, forcefully disconnect all controllers + if (!parameters.keep_controllers_connected) { + for (auto player : player_groupboxes) { + player->setChecked(false); + } + } + + CheckIfParametersMet(); + + resize(0, 0); +} + +QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; + +void QtControllerSelectorDialog::ApplyConfiguration() { + const bool pre_docked_mode = Settings::values.use_docked_mode; + Settings::values.use_docked_mode = ui->radioDocked->isChecked(); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); + + Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); +} + +void QtControllerSelectorDialog::CallConfigureInputDialog() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + ConfigureInputDialog dialog(this, max_supported_players, input_subsystem); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + dialog.ApplyConfiguration(); + + LoadConfiguration(); + CheckIfParametersMet(); +} + +void QtControllerSelectorDialog::CheckIfParametersMet() { + // Here, we check and validate the current configuration against all applicable parameters. + const auto& players = Settings::values.players; + + const auto num_connected_players = + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [this](const QGroupBox* player) { return player->isChecked(); }); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + // First, check against the number of connected players. + if (num_connected_players < min_supported_players || + num_connected_players > max_supported_players) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return; + } + + // Next, check against all connected controllers. + const auto all_controllers_compatible = [this] { + for (std::size_t index = 0; index < player_widgets.size(); ++index) { + // Skip controllers that are not used, we only care about the currently connected ones. + if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { + continue; + } + + const auto compatible = IsControllerCompatible( + GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()), + parameters); + + // If any controller is found to be incompatible, return false early. + if (!compatible) { + return false; + } + } + + // Reaching here means all currently connected controllers are compatible. + return true; + }(); + + if (!all_controllers_compatible) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return; + } + + parameters_met = true; + ui->buttonBox->setEnabled(parameters_met); +} + +void QtControllerSelectorDialog::SetSupportedControllers() { + const QString theme = [this] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + if (parameters.enable_single_mode && parameters.allow_handheld) { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); + } else { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); + } + + if (parameters.allow_dual_joycons) { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); + } else { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); + } + + if (parameters.allow_left_joycon) { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); + } else { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); + } + + if (parameters.allow_right_joycon) { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); + } else { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); + } + + if (parameters.allow_pro_controller) { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); + } else { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") + .arg(theme)); + } + + // enable_single_mode overrides min_players and max_players. + if (parameters.enable_single_mode) { + ui->numberSupportedLabel->setText(QStringLiteral("1")); + return; + } + + if (parameters.min_players == parameters.max_players) { + ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); + } else { + ui->numberSupportedLabel->setText( + QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); + } +} + +void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString stylesheet = [this, player_index] { + switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) { + case Settings::ControllerType::ProController: + return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/applet_handheld%0); "); + default: + return QString{}; + } + }(); + + const QString theme = [this] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); + player_labels[player_index]->hide(); +} + +void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { + auto& player = Settings::values.players[player_index]; + + player.controller_type = + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()); + player.connected = player_groupboxes[player_index]->isChecked(); + + // Player 2-8 + if (player_index != 0) { + UpdateController(player.controller_type, player_index, player.connected); + return; + } + + // Player 1 and Handheld + auto& handheld = Settings::values.players[8]; + // If Handheld is selected, copy all the settings from Player 1 to Handheld. + if (player.controller_type == Settings::ControllerType::Handheld) { + handheld = player; + handheld.connected = player_groupboxes[player_index]->isChecked(); + player.connected = false; // Disconnect Player 1 + } else { + player.connected = player_groupboxes[player_index]->isChecked(); + handheld.connected = false; // Disconnect Handheld + } + + UpdateController(player.controller_type, player_index, player.connected); + UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); +} + +void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked() || + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) == + Settings::ControllerType::Handheld) { + led_patterns_boxes[player_index][0]->setChecked(false); + led_patterns_boxes[player_index][1]->setChecked(false); + led_patterns_boxes[player_index][2]->setChecked(false); + led_patterns_boxes[player_index][3]->setChecked(false); + return; + } + + led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); + led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); + led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); + led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); +} + +void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { + if (!parameters.enable_border_color || player_index >= parameters.max_players || + player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { + return; + } + + player_groupboxes[player_index]->setStyleSheet( + player_groupboxes[player_index]->styleSheet().append( + QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " + "{ border: 1px solid rgba(%2, %3, %4, %5); }") + .arg(player_index + 1) + .arg(parameters.border_colors[player_index][0]) + .arg(parameters.border_colors[player_index][1]) + .arg(parameters.border_colors[player_index][2]) + .arg(parameters.border_colors[player_index][3]))); +} + +void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { + // Disallow changing the console mode if the controller type is handheld. + ui->radioDocked->setEnabled(!is_handheld); + ui->radioUndocked->setEnabled(!is_handheld); + + ui->radioDocked->setChecked(Settings::values.use_docked_mode); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); + + // Also force into undocked mode if the controller type is handheld. + if (is_handheld) { + ui->radioUndocked->setChecked(true); + } +} + +void QtControllerSelectorDialog::DisableUnsupportedPlayers() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + switch (max_supported_players) { + case 0: + default: + UNREACHABLE(); + return; + case 1: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + ui->widgetSpacer4->hide(); + break; + case 2: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + break; + case 3: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + break; + case 4: + ui->widgetSpacer->hide(); + break; + case 5: + case 6: + case 7: + case 8: + break; + } + + for (std::size_t index = max_supported_players; index < player_widgets.size(); ++index) { + // Disconnect any unsupported players here and disable or hide them if applicable. + Settings::values.players[index].connected = false; + UpdateController(Settings::values.players[index].controller_type, index, false); + // Hide the player widgets when max_supported_controllers is less than or equal to 4. + if (max_supported_players <= 4) { + player_widgets[index]->hide(); + } + + // Disable and hide the following to prevent these from interaction. + player_widgets[index]->setDisabled(true); + connected_controller_checkboxes[index]->setDisabled(true); + connected_controller_labels[index]->hide(); + connected_controller_checkboxes[index]->hide(); + } +} + +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < player_widgets.size(); ++index) { + const auto connected = Settings::values.players[index].connected || + (index == 0 && Settings::values.players[8].connected); + player_groupboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex( + GetIndexFromControllerType(Settings::values.players[index].controller_type)); + } + + UpdateDockedState(Settings::values.players[8].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); +} + +QtControllerSelector::QtControllerSelector(GMainWindow& parent) { + connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, + &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, + &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); +} + +QtControllerSelector::~QtControllerSelector() = default; + +void QtControllerSelector::ReconfigureControllers( + std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const { + this->callback = std::move(callback); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + callback(); +} diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h new file mode 100644 index 0000000000..1ec290e6c5 --- /dev/null +++ b/src/yuzu/applets/controller.h @@ -0,0 +1,125 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <QDialog> +#include "core/frontend/applets/controller.h" + +class GMainWindow; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class QtControllerSelectorDialog; +} + +class QtControllerSelectorDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtControllerSelectorDialog(QWidget* parent, + Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_); + ~QtControllerSelectorDialog() override; + +private: + // Applies the current configuration. + void ApplyConfiguration(); + + // Initializes the "Configure Input" Dialog. + void CallConfigureInputDialog(); + + // Checks the current configuration against the given parameters and + // sets the value of parameters_met. + void CheckIfParametersMet(); + + // Sets the controller icons for "Supported Controller Types". + void SetSupportedControllers(); + + // Updates the controller icons per player. + void UpdateControllerIcon(std::size_t player_index); + + // Updates the controller state (type and connection status) per player. + void UpdateControllerState(std::size_t player_index); + + // Updates the LED pattern per player. + void UpdateLEDPattern(std::size_t player_index); + + // Updates the border color per player. + void UpdateBorderColor(std::size_t player_index); + + // Updates the console mode. + void UpdateDockedState(bool is_handheld); + + // Disables and disconnects unsupported players based on the given parameters. + void DisableUnsupportedPlayers(); + + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + + std::unique_ptr<Ui::QtControllerSelectorDialog> ui; + + // Parameters sent in from the backend HLE applet. + Core::Frontend::ControllerParameters parameters; + + InputCommon::InputSubsystem* input_subsystem; + + // This is true if and only if all parameters are met. Otherwise, this is false. + // This determines whether the "Ok" button can be clicked to exit the applet. + bool parameters_met{false}; + + // Widgets encapsulating the groupboxes and comboboxes per player. + std::array<QWidget*, 8> player_widgets; + + // Groupboxes encapsulating the controller icons and LED patterns per player. + std::array<QGroupBox*, 8> player_groupboxes; + + // Icons for currently connected controllers/players. + std::array<QWidget*, 8> connected_controller_icons; + + // Labels that represent the player numbers in place of the controller icons. + std::array<QLabel*, 8> player_labels; + + // LED patterns for currently connected controllers/players. + std::array<std::array<QCheckBox*, 4>, 8> led_patterns_boxes; + + // Comboboxes with a list of emulated controllers per player. + std::array<QComboBox*, 8> emulated_controllers; + + // Labels representing the number of connected controllers + // above the "Connected Controllers" checkboxes. + std::array<QLabel*, 8> connected_controller_labels; + + // Checkboxes representing the "Connected Controllers". + std::array<QCheckBox*, 8> connected_controller_checkboxes; +}; + +class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { + Q_OBJECT + +public: + explicit QtControllerSelector(GMainWindow& parent); + ~QtControllerSelector() override; + + void ReconfigureControllers(std::function<void()> callback, + Core::Frontend::ControllerParameters parameters) const override; + +signals: + void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const; + +private: + void MainWindowReconfigureFinished(); + + mutable std::function<void()> callback; +}; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui new file mode 100644 index 0000000000..d7db46613b --- /dev/null +++ b/src/yuzu/applets/controller.ui @@ -0,0 +1,2432 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtControllerSelectorDialog</class> + <widget class="QDialog" name="QtControllerSelectorDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>839</width> + <height>630</height> + </rect> + </property> + <property name="windowTitle"> + <string>Controller Applet</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="mainControllerApplet" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="topControllerApplet" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>10</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QWidget" name="controllersSupported" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_21"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="controllersSupportedLabel"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Supported Controller Types:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported1" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported2" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported3" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported4" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="controllerSupported5" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="playersSupported" native="true"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>70</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_20"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>16</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>16</number> + </property> + <item> + <widget class="QLabel" name="maxSupportedLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Players:</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="numberSupportedLabel"> + <property name="font"> + <font> + <pointsize>14</pointsize> + </font> + </property> + <property name="text"> + <string>1 - 8</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="controllerAppletHorizontalSpacer3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="middleControllerApplet" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="spacing"> + <number>5</number> + </property> + <item row="1" column="7"> + <widget class="QWidget" name="widgetPlayer4" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_27"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer4Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer4" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_15"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer4"> + <property name="text"> + <string>P4</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player4LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer4LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer4" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer4Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer4Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="3"> + <widget class="QWidget" name="widgetPlayer2" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_29"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer2Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer2" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_13"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer2"> + <property name="text"> + <string>P2</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player2LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer2LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer2" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer2Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer2Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QWidget" name="widgetPlayer1" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_30"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer1Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer1" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_12"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer1"> + <property name="text"> + <string>P1</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player1LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED1"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer1LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer1" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer1Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Handheld</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer1Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="8"> + <widget class="QWidget" name="widgetSpacer2" native="true"> + <property name="minimumSize"> + <size> + <width>25</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_31"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="4"> + <widget class="QWidget" name="widgetSpacer4" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_33"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="6"> + <widget class="QWidget" name="widgetSpacer3" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_32"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="5"> + <widget class="QWidget" name="widgetPlayer3" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_28"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer3Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer3" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_14"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer3"> + <property name="text"> + <string>P3</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player3LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer3LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer3" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer3Emulated"> + <property name="editable"> + <bool>false</bool> + </property> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer3Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QWidget" name="widgetSpacer5" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_34"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletVerticalSpacer3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>25</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="6" column="5"> + <widget class="QWidget" name="widgetPlayer7" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_25"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer7Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer7" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_18"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer7"> + <property name="text"> + <string>P7</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player7LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer7LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer7" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer7Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer7Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="7"> + <widget class="QWidget" name="widgetPlayer8" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_26"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer8Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer8" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_19"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer8"> + <property name="text"> + <string>P8</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player8LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer8LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer8" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer8Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer8Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="1"> + <widget class="QWidget" name="widgetPlayer5" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_23"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer5Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer5" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_16"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer5"> + <property name="text"> + <string>P5</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player5LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED1"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer5LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer5" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer5Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer5Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="6" column="3"> + <widget class="QWidget" name="widgetPlayer6" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_24"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="groupPlayer6Connected"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>100</height> + </size> + </property> + <property name="title"> + <string/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>14</number> + </property> + <property name="topMargin"> + <number>7</number> + </property> + <property name="rightMargin"> + <number>14</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QWidget" name="controllerPlayer6" native="true"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_17"> + <property name="topMargin"> + <number>16</number> + </property> + <item alignment="Qt::AlignHCenter|Qt::AlignVCenter"> + <widget class="QLabel" name="labelPlayer6"> + <property name="text"> + <string>P6</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QWidget" name="Player6LEDs" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED1"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED2"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED3"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxPlayer6LED4"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="fakeSpacerPlayer6" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>10</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer6Emulated"> + <item> + <property name="text"> + <string>Pro Controller</string> + </property> + </item> + <item> + <property name="text"> + <string>Dual Joycons</string> + </property> + </item> + <item> + <property name="text"> + <string>Left Joycon</string> + </property> + </item> + <item> + <property name="text"> + <string>Right Joycon</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPlayer6Profile"> + <item> + <property name="text"> + <string>Use Current Config</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="10" column="1"> + <widget class="QWidget" name="widgetSpacer" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_22"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletVerticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>25</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="2"> + <widget class="QWidget" name="widgetSpacer6" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QWidget" name="widgetSpacer7" native="true"> + <property name="minimumSize"> + <size> + <width>25</width> + <height>0</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_16"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletHorizontalSpacer4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="2" column="1"> + <widget class="QWidget" name="widgetSpacer9" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>25</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_17"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="controllerAppletVerticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>25</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="bottomControllerApplet" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <property name="spacing"> + <number>15</number> + </property> + <property name="leftMargin"> + <number>15</number> + </property> + <property name="topMargin"> + <number>8</number> + </property> + <property name="rightMargin"> + <number>15</number> + </property> + <property name="bottomMargin"> + <number>15</number> + </property> + <item> + <widget class="QGroupBox" name="handheldGroup"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="title"> + <string>Console Mode</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <widget class="QRadioButton" name="radioDocked"> + <property name="text"> + <string>Docked</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="radioUndocked"> + <property name="text"> + <string>Undocked</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroup"> + <property name="title"> + <string>Vibration</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QSpinBox" name="vibrationSpin"> + <property name="minimumSize"> + <size> + <width>65</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>65</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>200</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="motionGroup"> + <property name="title"> + <string>Motion</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="motionButton"> + <property name="minimumSize"> + <size> + <width>57</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>55</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="inputConfigGroup"> + <property name="title"> + <string>Input Config</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="inputConfigButton"> + <property name="maximumSize"> + <size> + <width>65</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 55px;</string> + </property> + <property name="text"> + <string>Open</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="connectedControllers" native="true"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="spacing"> + <number>3</number> + </property> + <item row="1" column="4"> + <widget class="QCheckBox" name="checkboxPlayer4Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelControllers"> + <property name="text"> + <string>Controllers</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="checkboxPlayer2Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelConnectedPlayer1"> + <property name="text"> + <string>1</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QCheckBox" name="checkboxPlayer3Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="checkboxPlayer1Connected"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="labelConnectedPlayer2"> + <property name="text"> + <string>2</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="labelConnectedPlayer4"> + <property name="text"> + <string>4</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="labelConnectedPlayer3"> + <property name="text"> + <string>3</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="labelConnected"> + <property name="text"> + <string>Connected</string> + </property> + </widget> + </item> + <item row="1" column="7"> + <widget class="QCheckBox" name="checkboxPlayer7Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QLabel" name="labelConnectedPlayer5"> + <property name="text"> + <string>5</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="6"> + <widget class="QCheckBox" name="checkboxPlayer6Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QLabel" name="labelConnectedPlayer7"> + <property name="text"> + <string>7</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QCheckBox" name="checkboxPlayer5Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="6"> + <widget class="QLabel" name="labelConnectedPlayer6"> + <property name="text"> + <string>6</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="8"> + <widget class="QLabel" name="labelConnectedPlayer8"> + <property name="text"> + <string>8</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="8"> + <widget class="QCheckBox" name="checkboxPlayer8Connected"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="controllerAppletHorizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignBottom"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>QtControllerSelectorDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index ae3e31762c..3befcc7396 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -70,7 +70,7 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { +void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, std::size_t max_players) { player_controllers = { new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), @@ -93,6 +93,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; + std::array<QLabel*, 8> player_connected_labels = { + ui->label, ui->label_3, ui->label_4, ui->label_5, + ui->label_6, ui->label_7, ui->label_8, ui->label_9, + }; + for (std::size_t i = 0; i < player_tabs.size(); ++i) { player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); @@ -112,6 +117,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { player_controllers[i]->ConnectPlayer(state == Qt::Checked); }); + + // Remove/hide all the elements that exceed max_players, if applicable. + if (i >= max_players) { + ui->tabWidget->removeTab(static_cast<int>(max_players)); + player_connected[i]->hide(); + player_connected_labels[i]->hide(); + } } // Only the first player can choose handheld mode so connect the signal just to player 1 connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged, @@ -175,8 +187,7 @@ void ConfigureInput::RetranslateUI() { void ConfigureInput::LoadConfiguration() { LoadPlayerControllerIndices(); - UpdateDockedState(Settings::values.players[0].controller_type == - Settings::ControllerType::Handheld); + UpdateDockedState(Settings::values.players[8].connected); ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); } @@ -208,14 +219,14 @@ void ConfigureInput::RestoreDefaults() { } void ConfigureInput::UpdateDockedState(bool is_handheld) { - // If the controller type is handheld only, disallow changing docked mode + // Disallow changing the console mode if the controller type is handheld. ui->radioDocked->setEnabled(!is_handheld); ui->radioUndocked->setEnabled(!is_handheld); ui->radioDocked->setChecked(Settings::values.use_docked_mode); ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); - // If its handheld only, force docked mode off (since you can't play handheld in a dock) + // Also force into undocked mode if the controller type is handheld. if (is_handheld) { ui->radioUndocked->setChecked(true); } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index d08a24f96a..0e8b2fd4ee 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -37,7 +37,7 @@ public: ~ConfigureInput() override; /// Initializes the input dialog with the given input subsystem. - void Initialize(InputCommon::InputSubsystem* input_subsystem_); + void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8); /// Save all button configurations to settings file. void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp new file mode 100644 index 0000000000..1866003c28 --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.cpp @@ -0,0 +1,37 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_input_dialog.h" +#include "yuzu/configuration/configure_input_dialog.h" + +ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players, + InputCommon::InputSubsystem* input_subsystem) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()), + input_widget(new ConfigureInput(this)) { + ui->setupUi(this); + + input_widget->Initialize(input_subsystem, max_players); + + ui->inputLayout->addWidget(input_widget); + + RetranslateUI(); +} + +ConfigureInputDialog::~ConfigureInputDialog() = default; + +void ConfigureInputDialog::ApplyConfiguration() { + input_widget->ApplyConfiguration(); +} + +void ConfigureInputDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureInputDialog::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h new file mode 100644 index 0000000000..d1bd865f9e --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.h @@ -0,0 +1,38 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> +#include "yuzu/configuration/configure_input.h" + +class QPushButton; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureInputDialog; +} + +class ConfigureInputDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players, + InputCommon::InputSubsystem* input_subsystem); + ~ConfigureInputDialog() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureInputDialog> ui; + + ConfigureInput* input_widget; +}; diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_dialog.ui new file mode 100644 index 0000000000..b92ddb2001 --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.ui @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputDialog</class> + <widget class="QDialog" name="ConfigureInputDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>70</width> + <height>540</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Input</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>2</number> + </property> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout" name="inputLayout"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureInputDialog</receiver> + <slot>accept()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a1b61d119c..2ac8344a36 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -11,6 +11,7 @@ #endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/controller.h" #include "applets/error.h" #include "applets/profile_select.h" #include "applets/software_keyboard.h" @@ -19,7 +20,9 @@ #include "configuration/configure_per_game.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" +#include "core/frontend/applets/controller.h" #include "core/frontend/applets/general_frontend.h" +#include "core/frontend/applets/software_keyboard.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -84,7 +87,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" -#include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" @@ -283,6 +285,19 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) { + QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get()); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + emit ControllerSelectorReconfigureFinished(); + + UpdateStatusButtons(); +} + void GMainWindow::ProfileSelectorSelectProfile() { const Service::Account::ProfileManager manager; int index = 0; @@ -291,10 +306,12 @@ void GMainWindow::ProfileSelectorSelectProfile() { dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); + if (dialog.exec() == QDialog::Rejected) { emit ProfileSelectorFinishedSelection(std::nullopt); return; } + index = dialog.GetIndex(); } @@ -966,13 +983,14 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetFilesystem(vfs); system.SetAppletFrontendSet({ - nullptr, // Parental Controls - std::make_unique<QtErrorDisplay>(*this), // - nullptr, // Photo Viewer - std::make_unique<QtProfileSelector>(*this), // - std::make_unique<QtSoftwareKeyboard>(*this), // - std::make_unique<QtWebBrowser>(*this), // - nullptr, // E-Commerce + std::make_unique<QtControllerSelector>(*this), // Controller Selector + nullptr, // E-Commerce + std::make_unique<QtErrorDisplay>(*this), // Error Display + nullptr, // Parental Controls + nullptr, // Photo Viewer + std::make_unique<QtProfileSelector>(*this), // Profile Selector + std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard + std::make_unique<QtWebBrowser>(*this), // Web Browser }); system.RegisterHostThread(); @@ -2047,6 +2065,7 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); + qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters"); qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0ce66a1caf..afcfa68a9b 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -37,6 +37,7 @@ enum class InstalledEntryType; class GameListPlaceholder; namespace Core::Frontend { +struct ControllerParameters; struct SoftwareKeyboardParameters; } // namespace Core::Frontend @@ -116,9 +117,12 @@ signals: void UpdateInstallProgress(); + void ControllerSelectorReconfigureFinished(); + void ErrorDisplayFinished(); void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid); + void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); @@ -127,6 +131,8 @@ signals: public slots: void OnLoadComplete(); + void ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters); void ErrorDisplayDisplayError(QString body); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); From 5ce3015945e327751a053f7a5a1331a33f07819e Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 27 Aug 2020 01:21:48 -0400 Subject: [PATCH 02/12] applets/controller: Implement "Explain Text" "Explain Text" is additional text that is shown for each player in the controller applet. --- src/core/frontend/applets/controller.h | 3 + .../hle/service/am/applets/controller.cpp | 37 ++- src/core/hle/service/am/applets/controller.h | 5 +- src/yuzu/applets/controller.cpp | 20 ++ src/yuzu/applets/controller.h | 6 + src/yuzu/applets/controller.ui | 258 +++++++++++++++++- 6 files changed, 304 insertions(+), 25 deletions(-) diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h index 0908f2b696..a227f15cd0 100644 --- a/src/core/frontend/applets/controller.h +++ b/src/core/frontend/applets/controller.h @@ -11,6 +11,7 @@ namespace Core::Frontend { using BorderColor = std::array<u8, 4>; +using ExplainText = std::array<char, 0x81>; struct ControllerParameters { s8 min_players{}; @@ -19,6 +20,8 @@ struct ControllerParameters { bool enable_single_mode{}; bool enable_border_color{}; std::vector<BorderColor> border_colors{}; + bool enable_explain_text{}; + std::vector<ExplainText> explain_text{}; bool allow_pro_controller{}; bool allow_handheld{}; bool allow_dual_joycons{}; diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index 2a45a388ff..63c85256d1 100644 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/src/core/hle/service/am/applets/controller.cpp @@ -19,8 +19,8 @@ namespace Service::AM::Applets { [[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102}; static Core::Frontend::ControllerParameters ConvertToFrontendParameters( - ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, - std::vector<IdentificationColor> identification_colors) { + ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, + std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) { HID::Controller_NPad::NPadType npad_style_set; npad_style_set.raw = private_arg.style_set; @@ -31,6 +31,8 @@ static Core::Frontend::ControllerParameters ConvertToFrontendParameters( .enable_single_mode = header.enable_single_mode, .enable_border_color = header.enable_identification_color, .border_colors = identification_colors, + .enable_explain_text = enable_text, + .explain_text = text, .allow_pro_controller = npad_style_set.pro_controller == 1, .allow_handheld = npad_style_set.handheld == 1, .allow_dual_joycons = npad_style_set.joycon_dual == 1, @@ -126,31 +128,38 @@ void Controller::Execute() { case LibraryAppletVersion::Version5: return ConvertToFrontendParameters( controller_private_arg, controller_user_arg_old.header, + controller_user_arg_old.enable_explain_text, std::vector<IdentificationColor>( controller_user_arg_old.identification_colors.begin(), - controller_user_arg_old.identification_colors.end())); + controller_user_arg_old.identification_colors.end()), + std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(), + controller_user_arg_old.explain_text.end())); case LibraryAppletVersion::Version7: default: return ConvertToFrontendParameters( controller_private_arg, controller_user_arg_new.header, + controller_user_arg_new.enable_explain_text, std::vector<IdentificationColor>( controller_user_arg_new.identification_colors.begin(), - controller_user_arg_new.identification_colors.end())); + controller_user_arg_new.identification_colors.end()), + std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(), + controller_user_arg_new.explain_text.end())); } }(); is_single_mode = parameters.enable_single_mode; - LOG_DEBUG( - Service_HID, - "Controller Parameters: min_players={}, max_players={}, keep_controllers_connected={}, " - "enable_single_mode={}, enable_border_color={}, allow_pro_controller={}, " - "allow_handheld={}, allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", - parameters.min_players, parameters.max_players, parameters.keep_controllers_connected, - parameters.enable_single_mode, parameters.enable_border_color, - parameters.allow_pro_controller, parameters.allow_handheld, - parameters.allow_dual_joycons, parameters.allow_left_joycon, - parameters.allow_right_joycon); + LOG_DEBUG(Service_HID, + "Controller Parameters: min_players={}, max_players={}, " + "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, " + "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, " + "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", + parameters.min_players, parameters.max_players, + parameters.keep_controllers_connected, parameters.enable_single_mode, + parameters.enable_border_color, parameters.enable_explain_text, + parameters.allow_pro_controller, parameters.allow_handheld, + parameters.allow_dual_joycons, parameters.allow_left_joycon, + parameters.allow_right_joycon); frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters); break; diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h index 90a78d5082..31ba2af4fe 100644 --- a/src/core/hle/service/am/applets/controller.h +++ b/src/core/hle/service/am/applets/controller.h @@ -16,6 +16,7 @@ class System; namespace Service::AM::Applets { using IdentificationColor = std::array<u8, 4>; +using ExplainText = std::array<char, 0x81>; enum class LibraryAppletVersion : u32_le { Version3 = 0x3, // 1.0.0 - 2.3.0 @@ -65,7 +66,7 @@ struct ControllerSupportArgOld { ControllerSupportArgHeader header{}; std::array<IdentificationColor, 4> identification_colors{}; bool enable_explain_text{}; - std::array<std::array<char, 0x81>, 4> explain_text{}; + std::array<ExplainText, 4> explain_text{}; }; static_assert(sizeof(ControllerSupportArgOld) == 0x21C, "ControllerSupportArgOld has incorrect size."); @@ -75,7 +76,7 @@ struct ControllerSupportArgNew { ControllerSupportArgHeader header{}; std::array<IdentificationColor, 8> identification_colors{}; bool enable_explain_text{}; - std::array<std::array<char, 0x81>, 8> explain_text{}; + std::array<ExplainText, 8> explain_text{}; }; static_assert(sizeof(ControllerSupportArgNew) == 0x430, "ControllerSupportArgNew has incorrect size."); diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index 8ccf61be0a..7482174c62 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -4,6 +4,7 @@ #include <algorithm> +#include "common/string_util.h" #include "core/core.h" #include "core/hle/lock.h" #include "core/hle/service/hid/controllers/npad.h" @@ -45,6 +46,7 @@ void UpdateController(Settings::ControllerType controller_type, std::size_t npad npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); } +// Returns true if the given controller type is compatible with the given parameters. bool IsControllerCompatible(Settings::ControllerType controller_type, Core::Frontend::ControllerParameters parameters) { switch (controller_type) { @@ -140,6 +142,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( ui->checkboxPlayer8LED4}, }}; + explain_text_labels = { + ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain, + ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain, + ui->labelPlayer7Explain, ui->labelPlayer8Explain, + }; + emulated_controllers = { ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, @@ -200,6 +208,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( Settings::ControllerType::Handheld); }); } + + SetExplainText(i); } connect(ui->inputConfigButton, &QPushButton::clicked, this, @@ -468,6 +478,16 @@ void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { .arg(parameters.border_colors[player_index][3]))); } +void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { + if (!parameters.enable_explain_text || player_index >= parameters.max_players) { + return; + } + + explain_text_labels[player_index]->setText(QString::fromStdString( + Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(), + parameters.explain_text[player_index].size()))); +} + void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { // Disallow changing the console mode if the controller type is handheld. ui->radioDocked->setEnabled(!is_handheld); diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index 1ec290e6c5..db59dd631a 100644 --- a/src/yuzu/applets/controller.h +++ b/src/yuzu/applets/controller.h @@ -59,6 +59,9 @@ private: // Updates the border color per player. void UpdateBorderColor(std::size_t player_index); + // Sets the "Explain Text" per player. + void SetExplainText(std::size_t player_index); + // Updates the console mode. void UpdateDockedState(bool is_handheld); @@ -94,6 +97,9 @@ private: // LED patterns for currently connected controllers/players. std::array<std::array<QCheckBox*, 4>, 8> led_patterns_boxes; + // Labels representing additional information known as "Explain Text" per player. + std::array<QLabel*, 8> explain_text_labels; + // Comboboxes with a list of emulated controllers per player. std::array<QComboBox*, 8> emulated_controllers; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui index d7db46613b..c4108a9790 100644 --- a/src/yuzu/applets/controller.ui +++ b/src/yuzu/applets/controller.ui @@ -468,13 +468,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer4" native="true"> + <widget class="QWidget" name="Player4Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_39"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer4Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -635,13 +665,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer2" native="true"> + <widget class="QWidget" name="Player2Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_37"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer2Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -806,13 +866,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer1" native="true"> + <widget class="QWidget" name="Player1Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_36"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer1Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -1086,13 +1176,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer3" native="true"> + <widget class="QWidget" name="Player3Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_38"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer3Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -1296,13 +1416,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer7" native="true"> + <widget class="QWidget" name="Player7Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_42"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer7Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -1463,13 +1613,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer8" native="true"> + <widget class="QWidget" name="Player8Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_35"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer8Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -1634,13 +1814,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer5" native="true"> + <widget class="QWidget" name="Player5Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_40"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer5Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -1801,13 +2011,43 @@ </widget> </item> <item> - <widget class="QWidget" name="fakeSpacerPlayer6" native="true"> + <widget class="QWidget" name="Player6Explain" native="true"> <property name="minimumSize"> <size> <width>0</width> <height>10</height> </size> </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_41"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelPlayer6Explain"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> </widget> </item> <item> @@ -2395,7 +2635,7 @@ <item alignment="Qt::AlignBottom"> <widget class="QDialogButtonBox" name="buttonBox"> <property name="enabled"> - <bool>false</bool> + <bool>true</bool> </property> <property name="standardButtons"> <set>QDialogButtonBox::Ok</set> From aeec0f8a38cbe247bbe619a69842700208ee2d79 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 27 Aug 2020 01:46:14 -0400 Subject: [PATCH 03/12] applets/controller: Make 8 a static constexpr value of NUM_PLAYERS Avoids repetitive usages of the int literal '8' or calls to player_widgets.size() --- src/yuzu/applets/controller.cpp | 15 ++++++++++----- src/yuzu/applets/controller.h | 22 ++++++++++++---------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index 7482174c62..4783446a80 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -171,14 +171,14 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; - for (std::size_t i = 0; i < player_widgets.size(); ++i) { + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { if (checked) { for (std::size_t index = 0; index <= i; ++index) { connected_controller_checkboxes[index]->setChecked(checked); } } else { - for (std::size_t index = i; index < player_widgets.size(); ++index) { + for (std::size_t index = i; index < NUM_PLAYERS; ++index) { connected_controller_checkboxes[index]->setChecked(checked); } } @@ -237,6 +237,11 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; void QtControllerSelectorDialog::ApplyConfiguration() { + // Update the controller state once more, just to be sure they are properly applied. + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + UpdateControllerState(index); + } + const bool pre_docked_mode = Settings::values.use_docked_mode; Settings::values.use_docked_mode = ui->radioDocked->isChecked(); OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); @@ -281,7 +286,7 @@ void QtControllerSelectorDialog::CheckIfParametersMet() { // Next, check against all connected controllers. const auto all_controllers_compatible = [this] { - for (std::size_t index = 0; index < player_widgets.size(); ++index) { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { // Skip controllers that are not used, we only care about the currently connected ones. if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { continue; @@ -535,7 +540,7 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() { break; } - for (std::size_t index = max_supported_players; index < player_widgets.size(); ++index) { + for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) { // Disconnect any unsupported players here and disable or hide them if applicable. Settings::values.players[index].connected = false; UpdateController(Settings::values.players[index].controller_type, index, false); @@ -553,7 +558,7 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() { } void QtControllerSelectorDialog::LoadConfiguration() { - for (std::size_t index = 0; index < player_widgets.size(); ++index) { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { const auto connected = Settings::values.players[index].connected || (index == 0 && Settings::values.players[8].connected); player_groupboxes[index]->setChecked(connected); diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index db59dd631a..6ab4bea096 100644 --- a/src/yuzu/applets/controller.h +++ b/src/yuzu/applets/controller.h @@ -79,36 +79,38 @@ private: InputCommon::InputSubsystem* input_subsystem; // This is true if and only if all parameters are met. Otherwise, this is false. - // This determines whether the "Ok" button can be clicked to exit the applet. + // This determines whether the "OK" button can be clicked to exit the applet. bool parameters_met{false}; + static constexpr std::size_t NUM_PLAYERS = 8; + // Widgets encapsulating the groupboxes and comboboxes per player. - std::array<QWidget*, 8> player_widgets; + std::array<QWidget*, NUM_PLAYERS> player_widgets; // Groupboxes encapsulating the controller icons and LED patterns per player. - std::array<QGroupBox*, 8> player_groupboxes; + std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes; // Icons for currently connected controllers/players. - std::array<QWidget*, 8> connected_controller_icons; + std::array<QWidget*, NUM_PLAYERS> connected_controller_icons; // Labels that represent the player numbers in place of the controller icons. - std::array<QLabel*, 8> player_labels; + std::array<QLabel*, NUM_PLAYERS> player_labels; // LED patterns for currently connected controllers/players. - std::array<std::array<QCheckBox*, 4>, 8> led_patterns_boxes; + std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes; // Labels representing additional information known as "Explain Text" per player. - std::array<QLabel*, 8> explain_text_labels; + std::array<QLabel*, NUM_PLAYERS> explain_text_labels; // Comboboxes with a list of emulated controllers per player. - std::array<QComboBox*, 8> emulated_controllers; + std::array<QComboBox*, NUM_PLAYERS> emulated_controllers; // Labels representing the number of connected controllers // above the "Connected Controllers" checkboxes. - std::array<QLabel*, 8> connected_controller_labels; + std::array<QLabel*, NUM_PLAYERS> connected_controller_labels; // Checkboxes representing the "Connected Controllers". - std::array<QCheckBox*, 8> connected_controller_checkboxes; + std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes; }; class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { From 72b2f5d34f2f24bdcb252d2158d43aa7f827e60b Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 27 Aug 2020 03:52:26 -0400 Subject: [PATCH 04/12] applets/controller: Load configuration prior to setting up connections This avoids unintentionally changing the states of elements while loading them in. --- src/yuzu/applets/controller.cpp | 46 +++++++++++++++++++-------------- src/yuzu/applets/controller.h | 6 ++--- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index 4783446a80..4920d2df64 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -171,7 +171,18 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; + // Setup/load everything prior to setting up connections. + // This avoids unintentionally changing the states of elements while loading them in. + SetSupportedControllers(); + DisableUnsupportedPlayers(); + LoadConfiguration(); + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + SetExplainText(i); + UpdateControllerIcon(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { if (checked) { for (std::size_t index = 0; index <= i; ++index) { @@ -208,8 +219,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( Settings::ControllerType::Handheld); }); } - - SetExplainText(i); } connect(ui->inputConfigButton, &QPushButton::clicked, this, @@ -218,10 +227,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QtControllerSelectorDialog::ApplyConfiguration); - SetSupportedControllers(); - DisableUnsupportedPlayers(); - LoadConfiguration(); - // If keep_controllers_connected is false, forcefully disconnect all controllers if (!parameters.keep_controllers_connected) { for (auto player : player_groupboxes) { @@ -249,6 +254,21 @@ void QtControllerSelectorDialog::ApplyConfiguration() { Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); } +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { + const auto connected = Settings::values.players[index].connected || + (index == 0 && Settings::values.players[8].connected); + player_groupboxes[index]->setChecked(connected); + connected_controller_checkboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex( + GetIndexFromControllerType(Settings::values.players[index].controller_type)); + } + + UpdateDockedState(Settings::values.players[8].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); +} + void QtControllerSelectorDialog::CallConfigureInputDialog() { const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; @@ -557,20 +577,6 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() { } } -void QtControllerSelectorDialog::LoadConfiguration() { - for (std::size_t index = 0; index < NUM_PLAYERS; ++index) { - const auto connected = Settings::values.players[index].connected || - (index == 0 && Settings::values.players[8].connected); - player_groupboxes[index]->setChecked(connected); - emulated_controllers[index]->setCurrentIndex( - GetIndexFromControllerType(Settings::values.players[index].controller_type)); - } - - UpdateDockedState(Settings::values.players[8].connected); - - ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); -} - QtControllerSelector::QtControllerSelector(GMainWindow& parent) { connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index 6ab4bea096..2d6d588c62 100644 --- a/src/yuzu/applets/controller.h +++ b/src/yuzu/applets/controller.h @@ -37,6 +37,9 @@ private: // Applies the current configuration. void ApplyConfiguration(); + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + // Initializes the "Configure Input" Dialog. void CallConfigureInputDialog(); @@ -68,9 +71,6 @@ private: // Disables and disconnects unsupported players based on the given parameters. void DisableUnsupportedPlayers(); - // Loads the current input configuration into the frontend applet. - void LoadConfiguration(); - std::unique_ptr<Ui::QtControllerSelectorDialog> ui; // Parameters sent in from the backend HLE applet. From 7299356f370a0981abed519e42343bb84cccb9c1 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 27 Aug 2020 05:33:46 -0400 Subject: [PATCH 05/12] applets/controller: Implement fallback applet for the SDL frontend Implement the fallback applet for the SDL frontend, connecting only the minimum amount of players required. --- src/core/frontend/applets/controller.cpp | 35 +++++++- src/core/hle/service/hid/controllers/npad.cpp | 88 ------------------- src/core/hle/service/hid/controllers/npad.h | 1 - 3 files changed, 34 insertions(+), 90 deletions(-) diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 0fbc7932ce..34eacbb455 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -27,11 +27,44 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb auto& players = Settings::values.players; + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + + // Disconnect Handheld first. + npad.DisconnectNPadAtIndex(8); + // Deduce the best configuration based on the input parameters. - for (std::size_t index = 0; index < players.size(); ++index) { + for (std::size_t index = 0; index < players.size() - 2; ++index) { // First, disconnect all controllers regardless of the value of keep_controllers_connected. // This makes it easy to connect the desired controllers. npad.DisconnectNPadAtIndex(index); + + // Only connect the minimum number of required players. + if (index >= min_supported_players) { + continue; + } + + // Connect controllers based on the following priority list from highest to lowest priority: + // Pro Controller -> Dual Joycons -> Left Joycon -> Right Joycon -> Handheld + if (parameters.allow_pro_controller) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index); + } else if (parameters.allow_dual_joycons) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index); + } else if (parameters.allow_left_joycon) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index); + } else if (parameters.allow_right_joycon) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index); + } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && + !Settings::values.use_docked_mode) { + // We should *never* reach here under any normal circumstances. + npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld), + index); + } else { + UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!"); + } } callback(); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index efb953d93c..a920189148 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -720,92 +720,4 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const return false; } -Controller_NPad::NPadControllerType Controller_NPad::DecideBestController( - NPadControllerType priority) const { - if (IsControllerSupported(priority)) { - return priority; - } - const auto is_docked = Settings::values.use_docked_mode; - if (is_docked && priority == NPadControllerType::Handheld) { - priority = NPadControllerType::JoyDual; - if (IsControllerSupported(priority)) { - return priority; - } - } - std::vector<NPadControllerType> priority_list; - switch (priority) { - case NPadControllerType::ProController: - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::Handheld: - priority_list.push_back(NPadControllerType::JoyDual); - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::JoyDual: - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::JoyLeft: - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::JoyRight: - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::Pokeball); - break; - case NPadControllerType::Pokeball: - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - break; - default: - priority_list.push_back(NPadControllerType::JoyDual); - if (!is_docked) { - priority_list.push_back(NPadControllerType::Handheld); - } - priority_list.push_back(NPadControllerType::ProController); - priority_list.push_back(NPadControllerType::JoyLeft); - priority_list.push_back(NPadControllerType::JoyRight); - priority_list.push_back(NPadControllerType::JoyDual); - break; - } - - const auto iter = std::find_if(priority_list.begin(), priority_list.end(), - [this](auto type) { return IsControllerSupported(type); }); - if (iter == priority_list.end()) { - UNIMPLEMENTED_MSG("Could not find supported controller!"); - return priority; - } - - return *iter; -} - } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 40c7633768..0f2d338573 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -318,7 +318,6 @@ private: void InitNewlyAddedController(std::size_t controller_idx); bool IsControllerSupported(NPadControllerType controller) const; - NPadControllerType DecideBestController(NPadControllerType priority) const; void RequestPadStateUpdate(u32 npad_id); u32 press_state{}; From 6597b3817cd1e03577185aea7eb88856e046dc4d Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 27 Aug 2020 11:58:28 -0400 Subject: [PATCH 06/12] main: Apply settings after applet configuration is complete. --- src/yuzu/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 2ac8344a36..68ad43a803 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -295,6 +295,10 @@ void GMainWindow::ControllerSelectorReconfigureControllers( emit ControllerSelectorReconfigureFinished(); + // Don't forget to apply settings. + Settings::Apply(); + config->Save(); + UpdateStatusButtons(); } From 371226448a93d0553ded77750eaccbffa4a799e4 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 27 Aug 2020 23:38:26 -0400 Subject: [PATCH 07/12] applets/controller: Modify heuristic to account for certain games Now left and right joycons have the same priority (meaning both needs to be supported by the game). Explanation of the new heuristic: Assign left joycons to even player indices and right joycons to odd player indices. We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and a right Joycon for Player 2 in 2 Player Assist mode. --- src/core/frontend/applets/controller.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 34eacbb455..715d9fffd8 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -44,19 +44,24 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb } // Connect controllers based on the following priority list from highest to lowest priority: - // Pro Controller -> Dual Joycons -> Left Joycon -> Right Joycon -> Handheld + // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld if (parameters.allow_pro_controller) { npad.AddNewControllerAt( npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index); } else if (parameters.allow_dual_joycons) { npad.AddNewControllerAt( npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index); - } else if (parameters.allow_left_joycon) { - npad.AddNewControllerAt( - npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index); - } else if (parameters.allow_right_joycon) { - npad.AddNewControllerAt( - npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index); + } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) { + // Assign left joycons to even player indices and right joycons to odd player indices. + // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and + // a right Joycon for Player 2 in 2 Player Assist mode. + if (index % 2 == 0) { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index); + } else { + npad.AddNewControllerAt( + npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index); + } } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && !Settings::values.use_docked_mode) { // We should *never* reach here under any normal circumstances. From f95ea04995cf6ad8ea212078078780eb3ecfd460 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 28 Aug 2020 09:02:50 -0400 Subject: [PATCH 08/12] applets/controller: Set min_players to have a minimum value of 1. - Some games like Shipped have a minimum requirement of 0 connected players and is undesired behavior. We must require a minimum of 1 player connected regardless of what games may ask. --- src/core/hle/service/am/applets/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index 63c85256d1..6edb357046 100644 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/src/core/hle/service/am/applets/controller.cpp @@ -25,7 +25,7 @@ static Core::Frontend::ControllerParameters ConvertToFrontendParameters( npad_style_set.raw = private_arg.style_set; return { - .min_players = header.player_count_min, + .min_players = std::max(s8(1), header.player_count_min), .max_players = header.player_count_max, .keep_controllers_connected = header.enable_take_over_connection, .enable_single_mode = header.enable_single_mode, From 1ec71b6ea05b1a2ce5f1f6de4b4a979db7218aab Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 28 Aug 2020 11:44:36 -0400 Subject: [PATCH 09/12] clang-format --- src/yuzu/applets/controller.cpp | 3 ++- src/yuzu/configuration/configure_input.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index 4920d2df64..f2690b8dcf 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -102,7 +102,8 @@ constexpr int GetIndexFromControllerType(Settings::ControllerType type) { } // namespace QtControllerSelectorDialog::QtControllerSelectorDialog( - QWidget* parent, Core::Frontend::ControllerParameters parameters_, InputCommon::InputSubsystem* input_subsystem_) + QWidget* parent, Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_) : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), parameters(std::move(parameters_)), input_subsystem(input_subsystem_) { ui->setupUi(this); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 3befcc7396..7ea17a4db3 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -70,7 +70,8 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, std::size_t max_players) { +void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, + std::size_t max_players) { player_controllers = { new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), From 076e4d44c3dbfef173e7fdc189144a554dcfe483 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 28 Aug 2020 12:45:15 -0400 Subject: [PATCH 10/12] Address feedback --- src/core/frontend/applets/controller.cpp | 2 ++ src/core/hle/service/am/applets/controller.cpp | 4 ++++ src/core/hle/service/am/applets/controller.h | 3 +++ src/yuzu/applets/controller.cpp | 5 +++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 715d9fffd8..31a5cb2cc6 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/assert.h" +#include "common/logging/log.h" #include "core/core.h" #include "core/frontend/applets/controller.h" #include "core/hle/service/hid/controllers/npad.h" diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index 6edb357046..2151da7834 100644 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/src/core/hle/service/am/applets/controller.cpp @@ -3,10 +3,14 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cstring> +#include "common/assert.h" +#include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/controller.h" +#include "core/hle/result.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/controller.h" #include "core/hle/service/hid/controllers/npad.h" diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h index 31ba2af4fe..f7bb3fba9c 100644 --- a/src/core/hle/service/am/applets/controller.h +++ b/src/core/hle/service/am/applets/controller.h @@ -5,7 +5,10 @@ #pragma once #include <array> +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" #include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index f2690b8dcf..c960eb3dde 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -4,6 +4,7 @@ #include <algorithm> +#include "common/assert.h" #include "common/string_util.h" #include "core/core.h" #include "core/hle/lock.h" @@ -15,6 +16,8 @@ #include "yuzu/configuration/configure_input_dialog.h" #include "yuzu/main.h" +namespace { + constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{ {1, 0, 0, 0}, {1, 1, 0, 0}, @@ -26,8 +29,6 @@ constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{ {0, 1, 1, 0}, }}; -namespace { - void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, bool connected) { Core::System& system{Core::System::GetInstance()}; From b65456b958f64dee6215962b11430f57f89dd5a8 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 28 Aug 2020 13:14:19 -0400 Subject: [PATCH 11/12] applets/controller: Resolve several compiler warnings Resolves -Wsign-compare and -Wunused-variable --- src/core/frontend/applets/controller.cpp | 3 ++- src/yuzu/applets/controller.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 31a5cb2cc6..4505da7580 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -29,7 +29,8 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb auto& players = Settings::values.players; - const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const std::size_t min_supported_players = + parameters.enable_single_mode ? 1 : parameters.min_players; // Disconnect Handheld first. npad.DisconnectNPadAtIndex(8); diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index c960eb3dde..9d45f2a01e 100644 --- a/src/yuzu/applets/controller.cpp +++ b/src/yuzu/applets/controller.cpp @@ -289,11 +289,9 @@ void QtControllerSelectorDialog::CallConfigureInputDialog() { void QtControllerSelectorDialog::CheckIfParametersMet() { // Here, we check and validate the current configuration against all applicable parameters. - const auto& players = Settings::values.players; - - const auto num_connected_players = + const auto num_connected_players = static_cast<int>( std::count_if(player_groupboxes.begin(), player_groupboxes.end(), - [this](const QGroupBox* player) { return player->isChecked(); }); + [this](const QGroupBox* player) { return player->isChecked(); })); const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; @@ -489,7 +487,8 @@ void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { } void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { - if (!parameters.enable_border_color || player_index >= parameters.max_players || + if (!parameters.enable_border_color || + player_index >= static_cast<std::size_t>(parameters.max_players) || player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { return; } @@ -506,7 +505,8 @@ void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { } void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) { - if (!parameters.enable_explain_text || player_index >= parameters.max_players) { + if (!parameters.enable_explain_text || + player_index >= static_cast<std::size_t>(parameters.max_players)) { return; } From 5043036688126eeaa71752cfc5885fba7626fe20 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 31 Aug 2020 00:27:19 -0400 Subject: [PATCH 12/12] Resolve spacing inconsistencies in style.qrc/qss files --- dist/icons/controller/controller.qrc | 20 ++--- dist/qt_themes/default/style.qss | 84 ++++++++--------- dist/qt_themes/qdarkstyle/style.qrc | 2 +- dist/qt_themes/qdarkstyle/style.qss | 90 +++++++++---------- .../qdarkstyle_midnight_blue/style.qrc | 2 +- .../qdarkstyle_midnight_blue/style.qss | 20 ++--- 6 files changed, 109 insertions(+), 109 deletions(-) diff --git a/dist/icons/controller/controller.qrc b/dist/icons/controller/controller.qrc index 76083a9ac1..1c4e960c0e 100644 --- a/dist/icons/controller/controller.qrc +++ b/dist/icons/controller/controller.qrc @@ -21,34 +21,34 @@ <file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file> <file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file> <file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file> - <file alias="applet_dual_joycon">applet_dual_joycon.png</file> + <file alias="applet_dual_joycon">applet_dual_joycon.png</file> <file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file> <file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file> - <file alias="applet_handheld">applet_handheld.png</file> + <file alias="applet_handheld">applet_handheld.png</file> <file alias="applet_handheld_dark">applet_handheld_dark.png</file> <file alias="applet_handheld_midnight">applet_handheld_midnight.png</file> <file alias="applet_pro_controller">applet_pro_controller.png</file> - <file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file> - <file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file> + <file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file> + <file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file> <file alias="applet_joycon_left">applet_single_joycon_left.png</file> - <file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file> - <file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file> + <file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file> + <file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file> <file alias="applet_joycon_right">applet_single_joycon_right.png</file> <file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file> <file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file> <file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file> <file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file> <file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file> - <file alias="applet_handheld_disabled">applet_handheld_disabled.png</file> + <file alias="applet_handheld_disabled">applet_handheld_disabled.png</file> <file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file> <file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file> - <file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file> + <file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file> <file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file> <file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file> - <file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file> + <file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file> <file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file> <file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file> - <file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file> + <file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file> <file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file> <file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file> </qresource> diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 5b05b436b2..b6dd2063d9 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -52,30 +52,30 @@ QGroupBox#groupPlayer5Connected:checked, QGroupBox#groupPlayer6Connected:checked, QGroupBox#groupPlayer7Connected:checked, QGroupBox#groupPlayer8Connected:checked { - background-color: #f5f5f5; + background-color: #f5f5f5; } QWidget#topControllerApplet { - border-bottom: 1px solid #828790 + border-bottom: 1px solid #828790 } QWidget#bottomPerGameInput, QWidget#bottomControllerApplet { - border-top: 1px solid #828790 + border-top: 1px solid #828790 } QWidget#topPerGameInput, QWidget#middleControllerApplet { - background-color: #fff; + background-color: #fff; } QWidget#topPerGameInput QComboBox, QWidget#middleControllerApplet QComboBox { - width: 123px; + width: 123px; } QWidget#connectedControllers { - background: transparent; + background: transparent; } QWidget#playersSupported, @@ -86,8 +86,8 @@ QWidget#controllerSupported3, QWidget#controllerSupported4, QWidget#controllerSupported5, QWidget#controllerSupported6 { - border: none; - background: transparent; + border: none; + background: transparent; } QGroupBox#groupPlayer1Connected, @@ -98,11 +98,11 @@ QGroupBox#groupPlayer5Connected, QGroupBox#groupPlayer6Connected, QGroupBox#groupPlayer7Connected, QGroupBox#groupPlayer8Connected { - border: 1px solid #828790; - border-radius: 3px; - padding: 0px; - min-height: 98px; - max-height: 98px; + border: 1px solid #828790; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; } QGroupBox#groupPlayer1Connected:unchecked, @@ -113,7 +113,7 @@ QGroupBox#groupPlayer5Connected:unchecked, QGroupBox#groupPlayer6Connected:unchecked, QGroupBox#groupPlayer7Connected:unchecked, QGroupBox#groupPlayer8Connected:unchecked { - border: 1px solid #d9d9d9; + border: 1px solid #d9d9d9; } QGroupBox#groupPlayer1Connected::title, @@ -124,14 +124,14 @@ QGroupBox#groupPlayer5Connected::title, QGroupBox#groupPlayer6Connected::title, QGroupBox#groupPlayer7Connected::title, QGroupBox#groupPlayer8Connected::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding-left: 0px; - padding-right: 0px; - padding-top: 1px; - margin-left: 0px; - margin-right: -4px; - margin-bottom: 4px; + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: 0px; + margin-right: -4px; + margin-bottom: 4px; } QCheckBox#checkboxPlayer1Connected, @@ -142,7 +142,7 @@ QCheckBox#checkboxPlayer5Connected, QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { - spacing: 0px; + spacing: 0px; } QWidget#Player1LEDs QCheckBox, @@ -153,7 +153,7 @@ QWidget#Player5LEDs QCheckBox, QWidget#Player6LEDs QCheckBox, QWidget#Player7LEDs QCheckBox, QWidget#Player8LEDs QCheckBox { - spacing: 0px; + spacing: 0px; } QWidget#Player1LEDs QCheckBox::indicator, @@ -164,9 +164,9 @@ QWidget#Player5LEDs QCheckBox::indicator, QWidget#Player6LEDs QCheckBox::indicator, QWidget#Player7LEDs QCheckBox::indicator, QWidget#Player8LEDs QCheckBox::indicator { - width: 6px; - height: 6px; - margin-left: 0px; + width: 6px; + height: 6px; + margin-left: 0px; } QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, @@ -177,8 +177,8 @@ QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { - width: 12px; - height: 12px; + width: 12px; + height: 12px; } QCheckBox#checkboxPlayer1Connected::indicator, @@ -189,8 +189,8 @@ QCheckBox#checkboxPlayer5Connected::indicator, QCheckBox#checkboxPlayer6Connected::indicator, QCheckBox#checkboxPlayer7Connected::indicator, QCheckBox#checkboxPlayer8Connected::indicator { - width: 14px; - height: 14px; + width: 14px; + height: 14px; } QGroupBox#groupPlayer1Connected::indicator, @@ -201,8 +201,8 @@ QGroupBox#groupPlayer5Connected::indicator, QGroupBox#groupPlayer6Connected::indicator, QGroupBox#groupPlayer7Connected::indicator, QGroupBox#groupPlayer8Connected::indicator { - width: 16px; - height: 16px; + width: 16px; + height: 16px; } QWidget#Player1LEDs QCheckBox::indicator:checked, @@ -230,10 +230,10 @@ QCheckBox#checkboxPlayer6Connected::indicator:checked, QCheckBox#checkboxPlayer7Connected::indicator:checked, QCheckBox#checkboxPlayer8Connected::indicator:checked, QGroupBox#groupConnectedController::indicator:checked { - border-radius: 2px; - border: 1px solid #929192; - background: #39ff14; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: #39ff14; + image: none; } QWidget#Player1LEDs QCheckBox::indicator:unchecked, @@ -261,10 +261,10 @@ QCheckBox#checkboxPlayer6Connected::indicator:unchecked, QCheckBox#checkboxPlayer7Connected::indicator:unchecked, QCheckBox#checkboxPlayer8Connected::indicator:unchecked, QGroupBox#groupConnectedController::indicator:unchecked { - border-radius: 2px; - border: 1px solid #929192; - background: transparent; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: transparent; + image: none; } QWidget#controllerPlayer1, @@ -275,5 +275,5 @@ QWidget#controllerPlayer5, QWidget#controllerPlayer6, QWidget#controllerPlayer7, QWidget#controllerPlayer8 { - background: transparent; + background: transparent; } diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc index ec07ba160d..2b91204f3c 100644 --- a/dist/qt_themes/qdarkstyle/style.qrc +++ b/dist/qt_themes/qdarkstyle/style.qrc @@ -52,6 +52,6 @@ <file>rc/radio_unchecked.png</file> </qresource> <qresource prefix="qdarkstyle"> - <file>style.qss</file> + <file>style.qss</file> </qresource> </RCC> diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 537558c1b6..66026e8be8 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1314,9 +1314,9 @@ QGroupBox#vibrationGroup::indicator { QGroupBox#motionGroup::title, QGroupBox#vibrationGroup::title { - spacing: 2px; - padding-left: 1px; - padding-right: 1px; + spacing: 2px; + padding-left: 1px; + padding-right: 1px; } QWidget#bottomPerGameInput, @@ -1330,30 +1330,30 @@ QGroupBox#groupPlayer5Connected:checked, QGroupBox#groupPlayer6Connected:checked, QGroupBox#groupPlayer7Connected:checked, QGroupBox#groupPlayer8Connected:checked { - background-color: #232629; + background-color: #232629; } QWidget#topPerGameInput, QWidget#middleControllerApplet { - background-color: #31363b; + background-color: #31363b; } QWidget#topPerGameInput QComboBox, QWidget#middleControllerApplet QComboBox { - width: 119px; + width: 119px; } QRadioButton#radioDocked { - margin-left: -3px; + margin-left: -3px; } QRadioButton#radioUndocked { - margin-right: 5px; + margin-right: 5px; } QWidget#connectedControllers { - background: transparent; + background: transparent; } QWidget#playersSupported, @@ -1364,8 +1364,8 @@ QWidget#controllerSupported3, QWidget#controllerSupported4, QWidget#controllerSupported5, QWidget#controllerSupported6 { - border: none; - background: transparent; + border: none; + background: transparent; } QGroupBox#groupPlayer1Connected, @@ -1376,12 +1376,12 @@ QGroupBox#groupPlayer5Connected, QGroupBox#groupPlayer6Connected, QGroupBox#groupPlayer7Connected, QGroupBox#groupPlayer8Connected { - border: 1px solid #76797c; - border-radius: 3px; - padding: 0px; - min-height: 98px; - max-height: 98px; - margin-top: 0px; + border: 1px solid #76797c; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; + margin-top: 0px; } QGroupBox#groupPlayer1Connected:unchecked, @@ -1392,7 +1392,7 @@ QGroupBox#groupPlayer5Connected:unchecked, QGroupBox#groupPlayer6Connected:unchecked, QGroupBox#groupPlayer7Connected:unchecked, QGroupBox#groupPlayer8Connected:unchecked { - border: 1px solid #54575b; + border: 1px solid #54575b; } QGroupBox#groupPlayer1Connected::title, @@ -1403,14 +1403,14 @@ QGroupBox#groupPlayer5Connected::title, QGroupBox#groupPlayer6Connected::title, QGroupBox#groupPlayer7Connected::title, QGroupBox#groupPlayer8Connected::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding-left: 0px; - padding-right: 0px; - padding-top: 1px; - margin-left: -2px; - margin-right: -4px; - margin-bottom: 6px; + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: -2px; + margin-right: -4px; + margin-bottom: 6px; } QCheckBox#checkboxPlayer1Connected, @@ -1421,7 +1421,7 @@ QCheckBox#checkboxPlayer5Connected, QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { - spacing: 0px; + spacing: 0px; } QWidget#Player1LEDs, @@ -1432,7 +1432,7 @@ QWidget#Player5LEDs, QWidget#Player6LEDs, QWidget#Player7LEDs, QWidget#Player8LEDs { - background: transparent; + background: transparent; } QWidget#Player1LEDs QCheckBox, @@ -1443,9 +1443,9 @@ QWidget#Player5LEDs QCheckBox, QWidget#Player6LEDs QCheckBox, QWidget#Player7LEDs QCheckBox, QWidget#Player8LEDs QCheckBox { - spacing: 0px; - margin-bottom: 0px; - margin-right: 0px; + spacing: 0px; + margin-bottom: 0px; + margin-right: 0px; } QWidget#Player1LEDs QCheckBox::indicator, @@ -1456,9 +1456,9 @@ QWidget#Player5LEDs QCheckBox::indicator, QWidget#Player6LEDs QCheckBox::indicator, QWidget#Player7LEDs QCheckBox::indicator, QWidget#Player8LEDs QCheckBox::indicator { - width: 6px; - height: 6px; - margin-left: 0px; + width: 6px; + height: 6px; + margin-left: 0px; } QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, @@ -1469,8 +1469,8 @@ QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { - width: 12px; - height: 12px; + width: 12px; + height: 12px; } QCheckBox#checkboxPlayer1Connected::indicator, @@ -1522,10 +1522,10 @@ QCheckBox#checkboxPlayer6Connected::indicator:checked, QCheckBox#checkboxPlayer7Connected::indicator:checked, QCheckBox#checkboxPlayer8Connected::indicator:checked, QGroupBox#groupConnectedController::indicator:checked { - border-radius: 2px; - border: 1px solid #929192; - background: #39ff14; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: #39ff14; + image: none; } QWidget#Player1LEDs QCheckBox::indicator:unchecked, @@ -1553,10 +1553,10 @@ QCheckBox#checkboxPlayer6Connected::indicator:unchecked, QCheckBox#checkboxPlayer7Connected::indicator:unchecked, QCheckBox#checkboxPlayer8Connected::indicator:unchecked, QGroupBox#groupConnectedController::indicator:unchecked { - border-radius: 2px; - border: 1px solid #929192; - background: transparent; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: transparent; + image: none; } QWidget#controllerPlayer1, @@ -1567,7 +1567,7 @@ QWidget#controllerPlayer5, QWidget#controllerPlayer6, QWidget#controllerPlayer7, QWidget#controllerPlayer8 { - background: transparent; + background: transparent; } /* touchscreen mapping widget */ diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc index 616aace739..579e73ece0 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc @@ -221,6 +221,6 @@ <file>rc/window_undock_pressed@2x.png</file> </qresource> <qresource prefix="qdarkstyle_midnight_blue"> - <file>style.qss</file> + <file>style.qss</file> </qresource> </RCC> diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index 9f6a0ff1df..c6318ba4ee 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -235,19 +235,19 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox --------------------------------------------------------------------------- */ QGroupBox { - font-weight: bold; - border: 1px solid #32414B; - border-radius: 4px; - margin-top: 12px; - padding: 4px; + font-weight: bold; + border: 1px solid #32414B; + border-radius: 4px; + margin-top: 12px; + padding: 4px; } QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding-left: 3px; - padding-right: 5px; - padding-top: 4px; + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 3px; + padding-right: 5px; + padding-top: 4px; } QGroupBox::indicator {