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 --- 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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 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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 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 zcmV-Y3a#~tP)E7)pP#2={bSb|lmQyZri zM@2_4FS}+V;Ezt7Y5f71EOB()QU}&8X zmMw?e4(ND6`g_5$Wy`*iN@=C4e%t5sTsD9H{N`)~Hq@B@UeMUs*a&)t2p1<3iKW>H z>Uf~v6$7wj$*-pvlX?~Ks+t=YE_|@g<+z5Nepd`YS=oX&MffEkBF?JCix+?0<+z5N z0VM{Ysi|ox@DlJXV?0l}7t-;;fR<94ii(Pfsg%AY!YQiES-7xtom+toH3O~~fRd7u zdSkdBKt!HgvSi75?u0e;98+QdN=r-EfW|;`jj`6mVzB{fqvMTZP7HwKIQIab09WnZ zyRXCzn?ubp?J-@6L?S-{`3sPA9CKA!*@LgUWqT-arD6a=q0knPCjqZ9&f4Y6m*==; zdnj-%F#yfYhf08VfQyE^Dt2utrRl1QMVL`qTDsY7AA^Cb6$9<67&HusDo+hpRqT3V z0GgYdBMfs@e9TM9w(VTU>m6?riLW5XXIX9D$&)94JbLu#vxl=P?v|KvIBed1|HJD9 zxe4@Kx;3(77w9zLv~CtuPDgZ9_wV$Bc&vJId3pIh{&d)a1)a-(e#1#00GXz#Cdhdp4b^J)9vu53Ow&iWE|MzGA^o^fzX0@VIGl(UD zBQx^yF8Qx9)=uEts+4Wt{`zu^@6W)wuH1132(tb2&-NFcHsOqf*V1v-)zuA0PAM!5 z-mUPMpn+Jd`mZvpsOW4fDX%%k+_mAxgU4|a*@`u{hf zp`q#AP$;xz>C*BQBHU|?v-XNBrcQn3we5aEFBpK&j8K(g;5&WIv+dbu#pCNf2G~$r zxwW9MxH5(HfJcNIaLkN>d1rSlS{=z`YD-H~X->|leBirnZOKTyw(7nC`OFa8AH1AC z5>pr`x=%IrxE(0EJ6>D;G=N&EHR70!1~>~^>!JOrR8V+Zq#zJ{*Y*b=!$oBr4u@MD z$C?AQiSUc^^70$pvOQoN_JI6D9QLNaYC3RW59m1t@WtctHh}lu`|x%#=I5eZ0z6RQ zFY>z?$MK?y3j@WE*#6*wl%@OQ(aQ5wIb{2T2MY>=3+n3XT0us1Q@##531uXJfX^6f zqT4|=H#bK_*ve3W#X(x>gKRQKq8$ zz4j$^n4|uPjK||&#$r|5o_+S2ud*3fC=^;d)V<=1CttAKG0vqMqE){;a%|Abwdh4J~wyn+>u?! zJC{;{KR8|BHck0T9ZyT0wpw2l`is`8qtB#L?8`=Q=?(8lRSaZR6$3cp{i#v8T10bH z<(kIk);odUrpL|U4{`@E7b|i3#+sU49uGfMWVTvncD92Y3RP@bG8&}g{vFF0Pq#|# zOpH37d7?7YXX(y@!lH*;O!9A{QXr|eg$x2^WeeU^G9>r3fE^v>0S>TeJ0a z*YVD!6pyd@7qF=?5d4*hJUl|JWGq&>6x!Pk*$gsGOG{Jvs8OS?QzhTBJgb12+42>S z$G-qBv~7D_to?I0j@o=RW&Iv_)MK30*$S#R!r`!ES=MZz0k|a+iOk8i&v*|>fgT0k zx=-WLszo(5HJ@cWsGeA`V8JJ#^V)whE3$Qh;?}_JSKL}ubbfE+y}mxN{Y9_a{@{UZ z1l1Sqs+edtRk6|3I+K*ey^i-bAR=1?{iE9fWs1+|?L0wIUteG9wta)=Ce(Xx@8h8K z(2$DNRy`DpR^RD%K$$Xc-n;{8RSe>~Dvn0i?e2Ks9EkvGfX`#E6{)gqI~tG2zwD=-YcMj9w+k6z97*ol^}*G7d6&&GCiQBEDt>DXQ8jtP+XD2*>N~^S6^Nni?8D zt*fhRUAnY_iz+@AdnZK20E9xJ{i-|wpej!sYp?i(Nen>8>2p_l#V1r^pt~yG zw{Kr)pmt7}J*F!a6&2Syj=lh-K(7jgLa+7L#tBz303{_QFQ!$ohzv*-pOA?G@Or%q zRe4WUKFZC_9kO=GaY;o*#duXcA-9W@oMf2rKlXGXEXt$J9smFU07*qoM6N<$f+sH& 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 zcmV;e2vYZnP)G+1 zphz$+3j$$gKhE1f?#}KkyMx0rvolTS`D^bz_rB+M-@WIY-<)%1ph%G-c|?Gj`4&-V zd1;>0K0a)OhdDc>J)ZxB0$AByHtfinZz^k~)c_F`2 z3Xki6)40i5k!LLh$4cD9oB`(KS4!dW9By)62RwP!Qcz3>j^HMzHqTlLj<4em-#x&W z^Q@)dcmnsW`5Q%7Jd9G{ZHlgVR8n2>;yi09ID!;i@u>XIyJETA0FJY1GQOUf6V!9Y z+P(>W9w{Ml`iSE=p9lU7Oa;CRJbk%!jVIpJ167sQNc0Xx?gp+0DM^-z=wVf^1Fjt& zqW7~PllsJh$6x?_<^KTY0+qm1z$W00ZQHhe@XmW5{WF&jlpZ}KA_q~fPCaXpLmpvg zLv7_7spkjZ7-wzgEyl=ZH-1*=AMAF3*$aO@!wTGh&T*3?(-mDbiDU1L3-{l=JO4K@BZ(*+NOqdM7V zC!I~0b7a~#iTtXmw({G!uJ|RO54f!*ykWXxcV!$W3(UR#D&ITreUun2F-cm&ofV4w z(i+bKul0JXRCUY6l5!E=75G`&QdQQjjb2$_U$qC=2s{dGZ4CMsG>1FyH%QK!mBNF- z7e4JeClZ_O)K(q=Xbc8k4MjSCF1EkQi|S$#d+!))|FE&Hsy)N2R|6HmjX?XFV8B17 zIx{2`?z|o3qKOH$Mg6G8n%klet*NUT_y_A2LAHB=**0F5>?m=Z4O3;Jy;aYb{N8bX zcs7e#(LnTyaY{)))%MPW7w)tEsMPj>p@7E)iR zRlSNnh_VNf?>UZRbD+xt4*;J4_uw|Kaiys?P*YxJrm1CWC>;GGV9U$Ql&0FMQ1Tk@ zg*OfOm{Gk<7WokH16y!Mt9`(s9M?Gvzu%t-wZ#?y3#{rF{r=(3H)~C03~U9q0h4hP zH12c7wx_$pT5fKvt^DzYW%*1Dzz=|4Yh6f+Bj%jjk|;52HfF1=cuX} z%wB|;L0%c~S%a9N3+)5I%3jmI(5bKdYkwmCRbU08rn8Qm!i^Oad%C01Ix&RD^4X3ZKg54t88sLHmOOXL*8UGd9g(G@#&kBz#FqpH#piKziTKHeRlm8}Z1Owtv5 zahq4pcThC1+c*pO*o*QzkJzQSi(;3CFf|m8Ey%jM!TA00rrPRZbH#watE;Qz#L4(g z7rRx_sbg7!FxD;+cuiW{I~H1NwwEyBreN^l?sPYdY5C;xQBhgaSXX%*cdobuSOUD* z5{^Eja9^f@GFd)}L?Y9A)OP^oe$!A>{Rc#!?Ip1z5{XR9ppK)Nipc_^qMM!d>yI(y ziZ=sS^e6OT(8C$VIojRbJz{Dh701~ywTIH}s{E#@F0juFVB@E2OzHIXJ?c9U3U{_z zpT6K!SDzd$z(jBFymNitaa%kRMK6wm}loe)_ zd5#S%ZLGi@q0R#Hopqbe(%&;FlNcqXlg^uXe>{0?%$dHLnwm4YQJf#1rMRznRLU9J zR~*cueg`uzTy$Y7}{5yQ9twlf{&qn#ZTl}OTkgxS9}p_ao?H~6u;d&3R|QjanTwT4~PkfD_RqN%51YYTg;ZLx+u+oxg9jNb?A)7;ZTVrz+$u8MM9Cto|~E8 z=({2OeTLrt&lGZ5s#>i~YU?y?26$o`pKx{c_8Z4cV_#QS9Iu$lB><4MaYuNRT3wRA zHa9j+pP&;61hYE4)!uc2FbvQ0{IhkMJ)532`wxi8ufXxolR>$mp2q`yYR9ZXfL4!e~^=FLo6cBzT&X z<0pSbacMbt@C*UqbUA0m2_gJ{`7^Utr_%`@01uw|>GV0Ati0ehdCEYA{Dp0s;#CsW z=WO2Um5LyhQsHTqi#9$D0H~_D=J6~D1$gu1k>W2Oa-#gEVCUX9Y@EYmZCms6AQp=O z0QJqS2vR8#6%hsi2v)0t@^V)xnaGH6?aYJ5C(IZczVGe2{VFtA4TVgKTOHkK?Y@Ik z=P%8Q$3$tOS(X)qOf{Ulco}79E5X@0WTmHJ@6OF2gt&FQ5cCcVLahn}XSYKe5rWaN zaqzqg%0LBJfgp?pAuKR?rIepJ2ZP}@0N@g5M@nKGveHxhIsk9DaI#Py%#YZt}GI_lye{=CX+PiP#u5s8c z=61!HtyYM|VvLNM5Ga@Xq(FbLa~uv9A4OA38(1NWf@jts|Ebl}aTWkLhXbW2&O#y% zK+%S^Fqtjr9Wdg2_0?I)3(2**X4KX>X5z@YIS?vG7i zXk-jQDkYZ0Xwlf(jBw08FRvwp9!+wE}iliMde4{%NgIF6f+vjCvggkyPf0wj_E z)HdD1@W=?(J(+_pLoc#3Qc-iG&Og8CjORv5EnDu&v!@;vQ&M~Wv5CXYWItajR zEK@j#gYWD#l(3(?d*E1P*tb7qXQb`JXY{7i{TM+>IgvAfdv2&cWwz1{#u}f9^rjD8R-p8L7=(O7H za+z$g)8UwYt4)#^FZXAC q6h%=KMNt$*Q4~c{6h%>#cl-fTTpMh&g>w1;0000fGJHP+2J)) zYT`g(+A-J=CgbDGl_fRfH2W~PFI zg4vCYjXg#SI#r;F4%oD5(=1)*r=0HC`1_ckH)HvpKKg|=bCy$uHI$~qHXF#u3mxxSqU4*(z{xxK2Y>Kuc0 zWu3GV0|33fy&D0%0^mX+Oxui;bZU^+R!ZRW`R3|6cMzeF8LKyJa6M_1uxyj|iUEL% zii$2F@EZUiqQ|PMt1mM~R`!`xVgSJ9ayD<(pj%jK#!dXA~U&=ms=xMF}k0Eije zrmHJ9JTU+e4u`#%=B{|sTj@&YiUO;tcXq5#ej&|@g=Ea!;k@=kUw;0a>FkO}CWahE z}CU0``RKo^aOa6TB;5n=ur3e_}(?uvby= z%b@{G;QSaVXAcWX_;CNrd%6I?>-9bd&`JPp;c&0h|Js3zjeFPB(RKU*KnW364t>s& zuKBB94Ei&ZT+`lC|Cg}>+f|MGW?6G`j`cnTV15c(K9y`< za&yT75<)oy;38&JKGV|ls3~fG8Nm6Ideey!?3-o%NzSp}zEHp~0Pu)*vKN)z3C6=D z_(#EJe}HU7rE`eIekrJ|qj^tKw`x~44h8&VS2c;0&nDUx3e?>+(iQJ&YB@_vdI=di z{Kp~`@C&=D$s~yZ0NB*>8wm1QooK!ZEY^W|TSs%<#4SAGUo7!>@(zpzi=V7)A@*_pfATkE~4bd5xMU3qy{CxEN^`(xhrR{uR|&6y&0RZ}reVjODeJ*>+| zY)x(mU?Y!+1vIa?O_M@9*+z)?|u_w7`{=7 zv8iQ??L}pwc}3cFnbuWVS@{{jKLfx-c(A&9(>0l%GgU?2&4}Z68dGqp1A5_9AVE%(2!A##M;MUUaCXwKlc< zhD?i$M0#C_*NXf5V<|c5Nza+v^K&p?55NjQ{etrE7}`FV7>2wZjN3${_ge>B{cp2L z`3{3t5#*{&i}ZLrk*RjY0wNz`;Qbj;q)_sPx5uGqY#oSx5Y5dwWGgDYk4@3n7o&8AH#zcy5OsO{|(>;hqLST9iyDjd>(?Izc&w2DtUam0v#Z(xyQ&?}gsAZtaK(5ov#yw)R7_lztTnmwC2orsz_WtIVjgtG zU0q!+qvOW}mkY^vCu>jCKSvd$LM`=cf-U|PMhi-nwQJWNPq<=$40pwcn(N;?&|3eW zF`5Vf3kE*1Em?Mfk&_z55LaxPDpzbMikmr&0)B@I4op% zWUUy$Jy)(+VaYkxd)5CRjKXvZvlP?I0PHhK!XpCo%sl7v=Q>(noZw;0*8(^=@##~8 zq>y#5?*HBoix;mJLVqLfieGZMT=jOfv;v^yrPnl);wUaYlXd)7K;9}kTKunr@q(b( zt_4e%7N#&hBgLyE2q~#{XMNL0CtWdbud1p#CmN++0Gy&0K8hs2B_&v}bZH^$IlBnh zeK63_W&(gq&$Zs$c`Up+8WD$Vs`gN>CH7)6;}bJSugH<+^CtFwlf%+)j+}JGWW5*965c1AbpgWpotoQOqIZho@N~HnpH%oP28aP z>)uySz(m&ZBLKNdEPTAj6iNJF$Vx0(3D@weW>bOJTk|Y{n*p@VoqNd*D_5@6jh-W` zHQ#i}v?~Ut>52h{-7D6m6xSJjuXtLn7$9-4m>)&l6?;6Ml+Kw{nYM`mfV{lC!CG-% zUcRBV;%U2;5@hOL@w8qsz^GdB4xi6A+laW+J~6;RtvG&zVz!i=#>enzru|k*;PraH z0??Zn;)*8|Z$_*b0C2nAA2DMM05H>|ldTocn8W};{Pwvawc;6-7+|<7K62!UJKZ=l z=8PGN&*v+al3xa(1H8!XcJH5Pj2X9L0HC6xqAB5ui6|{sJR=hW0A{m!12f)brVk1V s3Jm>RnD+R5zS+z?Bae&IoMxKvKh$><2EIXrBme*a07*qoM6N<$f^b^rV*mgE 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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 if(a&=V1fyL8vGyKby3G7g>Vx90000S?n1A 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 zcmV+z5$EoSP)Uco zI0idlz_7q>$R5e=rnVNA+OP#tC0pSLM`f$1+KWJuEX0`0U}Fph+Zf;0VI5=1y6=%R zlIGN0@7O;C<^-vCQ2vBVNfEV0BAODyr{0CBYr zDk>_NFr!-skZK781!zji^U?N`2R`{4fcP!+F(o&AZ|8a`AruH8{hTNS1TH=#j`cNE zRaM2W#_xiK*|u$)d`w^;qzG~&Ns4Pg&^Z(d6^P5OM2O%xzJ?dY_tzBW`W;rQ$jVt2 zK@hB3DUbMr3{2!aev*5PBCN|MVV$Y=!;e?S1IJ|d^NtNjEBRhYi#ikcs!%sef_~<&nOd% zb9Ry>S&rvx!{P9cPJjMU)m(n&6lT+w9pw_K{0{^HA}`13-CVvQ%VD!fznx)RAKXL3 z(N7N_Z*OkxR0)D0#036<Vs^ zZ+Gu)=^q$~2m0D>NHsxIsT^&-ch{3Q-%xVLuI+a@}KHTrL%pQx&Hp}NimwMq%4LcS;;1B1gjcDf4V;Rq~g#)LL)o!$7| z`}=U^aR>_`tiYmLn&=j@!q~KAYF!rp^lrSxew8Pl^evL8%TmQa{9qrN35@dU@)P5 zI2wUcsesdA2gmU#JDQ3Z&mk+r0l9)fBr-b`85|zL$fy@Jbq)BZzx^wmZ@hqkK@Z|w z>bNe=sNa<6`m9H3^COQa%1cXMQ7PoJ?)f3lCN-?6G@+-@4ZT*447(K_ zJ$=w?H8UGF7~u2!F)}iS^7W-K>a}x7kQl8>iIZolkdx`a4JE5)m9vZtXKEX<`Ib#M zTT_orM>+!j05+DEr1Z;kydmJ1zjpEB%oU2h(y%zE;0}w)V7T)7csLAE6cG%Kvn0E^NB71b5uL9zFeTIBb>~bvKu9oagY9Q<#^7!n~Xr*9L}$K@bEi zCL<_{!uN0Agnfs;MlcwHPN%^Sw%i6_Vlr-N?*dKJ$abcKn3lenO$Jkbk>l1+KEb}} zzGn&Jv|Ed(SDa*I%!hQV8Lz$h2XyrG!9CayK0fhiXtY`!JuM>LYKBIwf+&he*PdM-Wc`PmtG?EdXjs}?~J$Z*)!0&@tXRw;@Sb(ACsG_`f0cVG~sBg0do0D#Zy zg+ie~jjI{=Zn+JoJ~H-Lxly9YeSPC;N89Z zapGJpZn|O3l#N!cn$?LbCtpIrun8YZf*=qIje{2i#A2~o|z#}_%AQn%)GtkrP#>N}hVdIVK(Ahh474rK0h(===^ZMWq1`&xyG3xat^b-h% zK$0X@ugJ%mqKP{%5D3k%VOgdam6bEJm~qEE{*O7SB1y;?7UQAI z&ItfA84Imi1%Dt2r80S}l4dgCt#{so2w+wIvZQu*-*r3w_xJCDqA2|Mv6(BX-C_oD zz7g%6-4l-(0XVFdgnpb3D?DRf?AiM%D2hb7&5W!}#|)eCU`X-TW0AH01Du__Hba=4 z%=8k1AS1C@Oq5EcGbRZ^6yZ#_qA)ibkw_Q=oPcOF42mS6GZ>-QX|SQR7+!w>8Fp*Z z0q15pk(=dAT0TjBP9|RZ`BMqYZd|hp91B4S8C|{oxaZDWF&>FPCS?$bML^RuG%6Lg+;S6i8a2WRVvK9S)>}7W>#Z|GkdHq< zjOLDRs1ynuJ5`O|0XMeXx(S=hH(+!5hJ=33)it!1ttnm>2!;x$U!Nh2oMj3DAj|2n zboIG?PP=6)C?N>~X+}MUN5=3s58sEz=2i@PMj>ZpSdpI#K@i{%1YxsGOnh;U!{Cqy z0Kmv(FdB4=7C{h2xQB)z2qH+5gdhko8FY|IFDLuaSPWRp|sMJqF>JESOTRsfd>Q4qn%WVr3-4LEf4n?*==kaiZSU47<&Y6KASWkdphK4G;5Z)t{r3B)u5UzpXD7NkJ5kqo0k6OPA>2d5peYK`=tTIi>z=!y zR>;ua-T}MWh@IQ+Oz3xD$P;ur9LniWgG?$V^7Hbhf}5#3#%$0pztVxlWH8h=HnrL; zX{x#QHvr(nPrraKI1T{t1}^Iv=XkvN?%t`-Hx#d&72aQoXLJk}lM%c39mJ5wgTC$_ z@Z!X2jMwLbAc*+v$Z`DSu?NvRFo<-k8B(bPcUIh#(k~|n7#bcKC|a>R_sTM(R-Hfj z|I}z%IXlY`09F?+UshG`YDwAJC0aUrFyi&YJv;*Upl9m3AP8t`?EuFMxY*u>K6m0w zd^)jM95R^{-97yX`216%Oyc+Z(bGSOU}zlV0@9DO)#uw+7v?1#D=TNG0;?%uWD-e2 zW0D}S%;~VSb#{-=r(uPhO}YxOEDJhGSu#X%;&1kK5uzwU5QKUA>getn&0CgjN|whl z66ur%Bmh)w+hx>fRGG;f>a-eFG#>XyVzHDbie3X_$;UV^eEKduTkYRLGN`HI`N`x(v7^ z3{BH>r@Pb0UzU}9;%rUphSHLx`v!8B!LrPZg}6-$K@hN-O$)Bq(9(LbboI*oxh*dv zl}a-JfZY1fLprrunewP45v3)ovd`Akw=J~Z_2NuTeOqy1{;Z&NE);S$W6PF@)nukM zbDdVBUhu055}GCp@^aE^7BCrKFKQZ_x(k=**d;W*;EiX!PAhj5*w@nORhlf z3b`!XnXa#CZ0agmk?$P!`Xb?QWQ3-v`A1#f0vyMaEW;S}I*qKRv8gLN!>(7##b`9aVS5U~ zkw_ic+1r0qT)@)S(%$v)OaJ~%>UX&QhReAoEFR~ZTRMJqSJvnJ}ZQsds&R?8}3H?{c}W`A==G1sVl=Os>_hv8E;7#*N20{`i^d=YRIZPk#eU zdpYsor~ALkE?nJ9QDm-Gt(rPw81jsIY8x*6=f3*dzuWu98&~xkpzjiw%OwW1nhzC} zRKZ9kc?$W&%a@`kqP?rvb>@7-(@#I~)0ZdhXWbn;ckZMtnagjpS}j)Z=$P+fL)(#$ zKKgDl=4-|F?SH8)D9)=;snwc3_fY?9a`MZ`!~7+dSYnAKmRMqmC6@T};{O43hJRuM SbOYG{0000xnK~#90?VEdal-0GsfBSrsgoKy8L9kY>Rt15XAQY)MGqKQQ z62z;x7JVqL_O`wCRZ1bmwlY30kbvI1q}ATmThw;lT2e)UB=QI|pj3GzQ7Emi7F<-+ zN=tc@%*@&Mk7RH%nMr11yn0>!)?$(G+xzUZ&tB&{-#+_01E2CKpV9+3-D#w%(x*?K zp&K`DJkiq*eO!=i8!TVGd~htLzgE?Aa&ui1zW@F2pZYKy`=~M5HaLF#_;JvYB3v4Y zM3#OSPJJB6dd2`OS#rPInAnRzj;aYPT=;{!9`@@aC+islP*E|jNrbxr5wVXfUc7iv z5Bv3z)2+Y&oIH7QDX;}N+Zfj`dd{Vf2i6ln_?Vy-#o~|$w1E=ZK9~6M^MQ%!X<& z1@c5Cs_?v^zm4QZez$wWhOBa~i~}>NXvVOJjs6(qW&}_Q6tRN7HvJ1@r=E4zfCk_k zRVu2VeeM+%eG;_mHB13PlkQ0-o(aSNtF28ONztFcF$G7Jhhw>sMVSUDqriCl#eOhP zq2vR(pbEqk96@3hy?*wWuQ~~`9*A0Nru=r(*0UTN%Z-S6n<$3?VkUN~IbRRIQCHWT zNxyN%6wMfBB3b~@E60^{lIC-BBhpu0j8xSTjxrRH;RqL^j8N!J>x(7Y>LUW21WqXO zrKZrjmp^b|Cise{#4zgxdLv>{RYCW}RKnuOs;2sNFT`~#E0?VRy%qRVPL8Yaqs)pY z`%6c*sD4XT%0%pCR>X*^LFA}ld<~n|Hg?v&)4+_LH7mE*p;HH~>Oc`O( z`W$y47!3Bx&o9^kTn74Jd3pIA-5*+yB5r?Rfxtajaxwb1qj~z7&Sq|>Ye3$i6Zctk zkP)_==<5t@*iyHMG(iCf27{-qb#4M$KyIt5sw(OJSb7v~+FVm9z0Cj>HlZ@K-)ZOm z&KjhhF>YVUb%NZl;@sG@dCk^ryr`_KtN>XI9E`=HWAD84&cA2V{=;U1ccxb*`nbi; zZ3@*snMx<+jG0t4W0+tb07)!y#74iO!kWfVO+)rW zfM76ajggyGIV!@nOP5yNXdGSvd7#i=Hmy50`<*uHRP1x7a$_$N40iC_>-F{$m3oD5 z00BWCEA*9oC7r#7&1)M~VJSGL>fD01uDRNz>7FE}xl%^7O2+XA7;(iE)xu**@cV=_! z)UxwD-jX?Pf63R~Uz%}tDxC!j7CZ{N3bapdZr;i%W6ldSO`Y)bPMu9V2EMDXO@;4m zud6%SVIZ-bgCxBe6!~jsHccP13AjAfrjHYnS6HTF-FE(wiS&>S zhC$eqYs})feDthYxyfxB)~-DX+H^WkCnw7~1V%-!P;^^52R+`>uZqgOKu*H#0#SX) z;|okr)~~3jI1KuYR@2N2cosV7xzYg}bQu(hh=P(76494{{>i9 z&6+jq%trvlZWHJ{FkY|MCFq1mRJNycSmC^MG7-`)tfx<(Tmb9=E@^3rR;rQNsy1zD zz$&E*eSrc;O#4JpNS^xnjpkWnB$T^$*T4( z$nP~cox{NXPGtMi2@V5aIHU`zA-EXP6Njf^Rq;mz>XyKX2<5F-WZBL zpLFNXJN@pr;upD&3nkVv37Yvl6?+i}f@SQ)X;ODeHn#X`Q75!@K;tQUl8Dl5-N=wFjXPyPn zYSG`Ig5Sp!oUYzoyZvN5?7__o3~oS5tL znFIw|v*K7Rb|x#XtgO5#lXhJ()>}MEl?4g6hPy03Os7BIJM%1yR02!HunEMIC_gh} z3JVK!wBPW<(VWPz-5WNXaC-v>jPPbdsOF2^ade!oY_x;j)7Ho73kDbTsjjY$#pSI5 zkw@dT;);rjH@eeLoC)5*7%S{)cT;t3W4LxkDt(Wy^aZdZ8bh_89qpafCr?hkBS+sE z(9qBjfrJ?o>pN^T@MgU9=W1ywQ<2H;qSEWT#K%gY~&XT`=i zkFHv^D(!dliAC2Aa{B@gdHjLbJpRBRJ-*VavD2pYPSiPXNWq_g_HR>>M^o8z`%7jk zI?-sf6ku<-`Z#ca-oq}A>zoOUAccbNieK;i{l=HX@=LM$Yp>UPR_9^p9*J4;U;eUx zekz@@)20oL+Lo6E{te|qluJP8yCOL|5`$A+T^-BmWv2-&Qjtf1FE@s2o=Ub?c=gr& zjmj?#_P=(8H~b-fUy(*NXZB+WkCHF&prZR4L$&{rXvpIW{8f>+nnLR)bv4lOzLIMk zk;!d6eMaHT&Q*~IjcDsOLv?jSsCGL*Wo6};5^Kd36+hV7zW>t4LBF1=KM^V$HrFid z%CDsH>{1xs6sj4Oc)!pe_<>qRHim2FLc)wubgW`_CmV8^T)#mJCuC4d5#(A`%2g>( z3`(m^Z;#7RIkRr1%8J`t)wESWRRUf4l!(V$dW*n#C(kMBC^fqgE<1CT;QHuXWB!n= zzN3EiYYKN+s4Bd=>@(Rok}1hqak9sqG|l)E^Q7Y9QHcJhsNB2j*)^|rP>8hb0mim7 zTzKX55dhok*Swp`Xk(~mG4PsNyQ#CzMKugmxBk;D?PTywT&Bi0CCZYs;-p&f#LCtOurjL3J5wR4*4>fY;2*T5bz zjA{ED1Up779oCm*nnG)z6ztCox@uB!@u<#p+rBBsi47StMAIs_5ex>c<2W}0hk=># ztoY^4>)vYITr;~dR5PS8RC9J?sODQQtY80+uIydzoi*G#X0@OzcWtTtO;`FVvPU!_ zFd&y&$L#H@AwWZ@_7+6{9BDS&>16S13axwDYR-U2gn{`j&VaR<}cWr7>J{ z+tQ^~4~lRbZ6_#xwR&S8ahd-~(vE>11W}MVs`iIX;o5E;we|P|zfzTrE)_0P;q~tN z-#oZrd9U|QU61H=lzV|URmo_f?F@Cy5opbd^YaV5z-28h(aQeLsRc*0cf0IZ1PA0? zfx8uL?9a*N-R)PD*AczQC2+pBW~JRC@9!t7RAgU2Juxv}neR!0!Qkm7OYXnPnAnRV z+*(pn^6Y{I3sRO|3kwTZ(tpHLN9nT|$ZB1BAALhqN)0gFvF7dWnD9&uT!PA1TqgHh zA4X8(w9kq)?YZKHhK5L9PUI%wrg6Tq(cS4wMc+|nxB(dwb7Ft%mJx^L2#d3Mqji_} zvtk($&x%vgn)unJ4mNo+nh`N|y(_{{0}M4WSJr`X`%9(@T--dU;Gym~kS)nsaaC2- zoOHU)gM0rJxWw%*nVM}sF6CW?VFu{Wv4dUiIg$}8_n|D>TV36fZQJfLZ{EBEs(cSX zRaT_SiuYDmx1cN%m4DB=lh#=DH^344p4~s2wuSz(X(9}}U`Roi`;VTc?euwiEqJ{AlH3pywTgVv&pMD?Cj$J0_F!q*ge%IvywVm(5B zCQQ)WN18%4YqK9p&y$~@f2LNPpWi#>>-nWn?HW~%dA9Dny(@ceUtk8Rc}<~p zn+?IBRh8RN`_r-B^u?zM-oO|z3!^dTW}wTW)Bgpx-+p`Z`{#=FmNe&z)p0Qg<<8EM zzRRaxbB>@t5yP#-e+1DKT33(A11>RPkGJ>>$&K8;va77H9_9AkTi2#N>hV#KR4ZP& zY}vA*$vQhX)%-<8?{Ki|Q{^_}{edg26Wt=PyrI4(1RYL0l+8r!3RMn>unRzu%Rxt= z`klsb?Xw@!_&yMcCn$iY%gYyBn@Vqjue8LX52L(wW_-#7L`NxdYg4FZOYU_u&7wQQrD=SB}ed!n=!p*HGMmys5dR<3N{#3=ZK30}L zeQroWC^_}`z`t<&II(nT)r}%N))OZvy8UC(k0Y68#hLJl3JkQ*iudo|U*45|IzCa4 zQk-SWmR)VFZlJZ~Ii-?^AI-Nch$8q|+yu6Hy($;nz!^6XiUDvlxrBbO> zGX>ndbsO%U-o>itiOI=HxLj^Xk~Dkp0%2mYWaGCsdIq0=eg^lP_rWkU)M_;jyuBZS zAS`+vjKQe?&7zN*MNe-p?z`NeX&OUAL#U{D2^TN5;-?4RnX%uk+qV($PXYiG$f&BS zLTO3K3)R9XijsC@Ken{I3?;?3teq7_!IxLBVsOBN;gMmO%@&x=1*luU9_8ibb1r6L zVgd}qpyNh6?seWp@w*2x6`q>4U;B-((An9Ef&MmE@xpn?vW%&47_V&FIHz$lnQ`--Nz137B0Lqv=;$a0JRaofO$bM0aJ$`@icF#7 ztDETT?93_G<4x9TwW4X;HW>7IP;(sYCB@jiYiC;AvuK)z<(qxTx}@lNr!)-%N+pY; z2!@&Q=9$Lja$(%-gWvB*BAEiuYXATgMS<67G2!!r=QU_+@4(u%YqRG0=b5u`I`2c4 z6_l6T@%CH$QC+~&3y=mLNMi< zU^E)%xD3-NiXt<-T0QmP!2^9+S(zr+j{W@uU|ANSa2RSepVrQCYE1Yh04NBD!_e#X zX)(8M-NyZHHyDFc>m8WQ z7UY{ub18SuLZOgbHR1QSIi1}mMNx9@Sou-~L{Wr6Z-6AF-!;i(0)k+Lxv&5RgCS#% z4I4I~qM{thL>z)efVt3w?c2Ad#jz}lrlzgX>2%FH^sY&M&@ zVe{6m{rlcbIUJ6h5C6Y@^brD+L5z$HBNB^)SMyL51@U+cR>2CRL66s8Yr@MfXWx7Q zAeBlXl}bUY)dB#V&iiP)-i~-YhGLryd-v{naxesg!57|GXEQiBICkjJp_Ozv9FCTh zmfAz%Q2C}!n+kJHu&lBY-Q6zm8ZGj4I`}5MFd2=om)gOxEb7)fP+vd$^8`)PVA=nD zUh(_=X!-0Ek|`0Atl&{Fgn&PQ6)Tnl0C+xqqn~fBt*ysSo;-P$1^|gx4VVipztU14TteC|a@vul;BnYHDicT-2RAcQHCPjvMW5ICu68 zR;^wGMV3%o>zH${=iziZoyU$HdvA1fG{FGCpxf;(Eh_rMx%2<}#fs(26Z!f1>Rb|0 z6ooBY8nI>HvlHjPVL!sFMp68*}Y3C*PY(k+>ytTFUa7RZ+2mqcOD?=Vn zTX}iK&i=kWrMA|Q&oE5Zcc_J8u~;xRHj0On4`C4mP!zCx*N&_k_CaS2D%SG+So#mTkYa3YLQ!4IkgNbLZ&NrAuEC1YzON%mt80Bovp+ z+ZhyyUdL6W3}ecF0K zvMkF0yk4)@*WcgoYH4Y?4j}hOe1aedf*=TjAP9mW2!bF8f*`(!{tGl)*9jbl-DLm( 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 zcmV+u2<-QXP)2@bA68et``b^9a}&lLQj-z^F-+HME3; z(50K!q+rTYMv^9NqAf}{E$Wtb$>OS;I<1wasX}d`}D zpto|v0f`_A;+Vt=d6CQQSv1u=Q$bObpieb4_ye4Be2nAy^{m^sV6q%EP2c{cD6cwN z@g{(f6GH*Op+kqVDT-16z;(yK2A9jd=U#5$ewLJ=eH9SeluG4*TrPKnY9ota!t9=YVU2Az=)z4 zw3oT#kw@~7lA1DMf+njK_4QZaa=9Qvgwbfk!>b;GMx%+i80WwsSg#k)?cRkSJ+%q{ z`TTR-xDoQjF-A?zS;T9#C|XyDxBpZHK@d^2t}x>9M+@4=jJ9&cgJENVKmhN5@K+o^ zc^VAEz?g1;!|BAO%b(%QnX}lw?P=tiav~}wO|OSemx_0)s!?5i7+$Xr3oJ^Py7~t6_S&ITMuBBnyz%B+ z=D5pvlja@(9iCoiC7z}B6s;mqJ1^JWq3j_kNHn#u(u3TxntNR&`2?TgS6h%-} z*e5E;4Tl3KPSqebHU|Cu*THcd06-7~^!N8c6Bma7&*OB>S?qjvd)Pb|E?mT!v**C` zJo56sgU8md$Go|7v3u7~#cY{#eT2TgK4fKOq10RqOgspl92^8qQ#kj@KOvJzv2}CB zNE@@c7-sW5PsZpmS=#Fz?)P{+A$27i3L5)?&2uM0jH$Hc^7ZiW%| zUOUWXo6urwMVfv}2k5)Taa=2{)o3o()-}XU-u!?eb)4{Wk09YYV}j?eB|hdIQrp7h>MMbd&mu!%LSUIk(`tW5fmg6 z8ZYks1u~4r3G)bo08tci=eX9>(}PnrXW@3cVai#G!nJEgUJM+^g`Mc>;`Ms_YHMp3 zNiJW$>|MNMv4N&3V^(IScB%=oGUvm0%ZKKc7RaPhWM$5Wgkj+Euu!Yj*!j#hEXvNF zG!I2lBma?jy$hY z&y+7;wyYyLIXPo038d27Z-xhQb5UDY54U>=iAjmb_-5yukdP2@QC;2L=;-Xk&wut) zl$UQpVbOY6o13vZ|KW&p-HoB4A!|oR`)`3!e{DX#vG_ogQn~ZF-MivtGMRi<$0dB+pq64n~M+Kxi;qJ zckI|9{cX2HD7#Dzt(p3s=}CVaH&+Pm>I2bmT?^C8yp;b-{EjeZ z!qhc1zTk8WY|GEjzg4(4zc(r>Do-w#&+G#~Lj*w(hlYl1e!u^`)9HMxqN1X1qV|*i zRxK8bqO-GeWmQ$xzbh*%(-IRCS1FZB1H&*eJTJyhU+L3N6h(oi(eL;B9W2Y*%FD|y xQ4|&afJYDnK@bE%5ClOG1VIo4K@bFi{{kQq&s8%WdiMYT002ovPDHLkV1gPL*Vh05 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_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 zcmV<72_E)|P)IS<#@cdwsuU~=yYS;fdMea+y%S`EO_Ug zwhB9JPBi0MV-g94LN|iE2*ixB76bzK?y<}EL|{wC0Qmj>29SpUmt~oa_uqeird_rt z0^0%u(9vK|oY_Bwbap?STR4 z=;#QM=Bn8CsnkchtKyVo45Ys*PFY|ey;ZSuus`M%`R0ouYoyL1{BLqJ>p*mJWWm5T z3v2J^nX%)>n7b^?QNS0fxH`XR$=5M*X)=zAWsDf-enrkz;Q$JH_IF5OuLzgLoZZ*lR$M$kQ5A1$eEGlK z#_R{p9nBaCe1_^PSkiA=iegkbhEOnCLkjh+;X2s^g{11xo3m_*+r-$O< zmT=u&)zxc%C&EvGf4N*v&y!ns+$gZwrZGm#M+(mYi0WsGLs(-L7 z9_wpRWv?KU#^ZsCs=&*}h!&KToEMEoDl;>)yujS9u2`rwT(=^>sO)YFodwKC#WkMs zMunJTf<$_LmP~;`k1eYD7s)R!y;6+zTuZoaPOAM%7gg*o^y#p>lDlZhZ#{*+XRlhc zXo?*JCZ&pl!IbWpBQgBoYApx8>xen0*)?ECM@LA6CWcVO?xM0=+=afw?p=G@J%zqU zJswZ$r`RBj_+L>_I|mE~gC-u2->J$m5pD^ELd*KvJl>_#P<;xR31liP*QwKgGLlWV zxAfbdB44?u$XA|U=)1}5^$sdSl3_IIYWuMv5D2u1=!#w!HuR}tCm)`6y@;#Ve==*)Xx9oe2BA9-^f8#(^ zoE5d+1U~8ir0yKamJE20xFzFO*u$n=OL*HA!?hO_lxKEkL|#(#YLsjN5WPLW(C5$U zaun^}z2*3D{RgVz-MhC`=M^vYSw^eDOcRkOn|Ibflk{8JfYTr`=Li8Biq4G4IuJK# zV)~zB!8fCm%m!fDP<;UcfxyvFD6|~pMG<~oRkdzM?_F_m;&XZ4C3%*}1HddrHXYB1 zS03EHy|b@fMgM_HSMRS~PUAWE%=aNU|D{T&r~ z-kZ<0r2CN7>v*^4`jY9%7`V3hrd-T0Yl+a=eZrbKbhC4w!aZ*9lCo$tQc3S!aaUI? zl=$4i&rV!O|Db@%b;l1%s4c5L|=YnZKEne3Wk-XE&jvf`~k zOhq1j;gwf%jBzXjx~{UzsOuXi*9f1`m7gR~nH%MSzs2U9(EzIVwy5!r0iHD_EU=YrfVm_N?(mS?Bhk-@MW4m~c}6}?&V?5VmdE+{EEPee8fTCulj(@_{8rJKWb zbs&G8EFGJ@UT;P!of(WhP4ypvd3Rpn;>#yZa-FQ8ICSVxWva%!YSE%8ot@5YB5Z9A zf1$F!tV4o9%e`yQ6NpUBG416|O-+Md|D6gFDk$Q{So7_!pzthQda=gLpVg9ow(i{a zV-ju>zI=!b22GBhxC7PCkI9t3%`04dxt-1=gWvCOP-O$)w7G&pJVm~86F095JU=if zFhpf|TU(p?u7 z*f8eO+4AyoM@H@}-}Kp+J&ni>7J7SgQ~h&;Jv-v{fam&>={Ozt8srXvHxUV|;Y|m8 zYD9-UJ=;M{&5<9JEWBXgU2#E4$$6b!&e_BD71U{vuUc@S3I!;OfwvI}wd~xsiD53` zMtuo&NoncibEA%8RSHzNLecXD&KdO^jv0NrC0zfT>gu(tMW_P(FdmQ3eZKKkkE$8z zAQ=OW0EdBn>U3;tZP@a`2z`JQe$=;{Q>}{ACNR)n6^n3Z&$sc^Frl9)l4aGZRqv_t za{yI&FqIV)X%!fN-a4Cx_Y(3W|ig;y=~Y)FfM_H7IEx7=UOrvbMK^ zBG(vw*bbX%Kb86jg+dpC?8hcmd`4spfZy*wY^N$dV*&&As^T*$FwkEWx3#tT$7<({ zSz{8Zsi|3LjJ^cKK^OS_{-i(Yo^criP*G9Q*r$p`WK62~j0_Bb%jH_F$^lh*H#YH`I~S00000NkvXXu0mjfC=c39 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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 YZ;N;RsRi&(~Z20>1 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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 @@ single_joycon_right_vertical.png single_joycon_right_vertical_dark.png single_joycon_right_vertical_midnight.png + applet_dual_joycon.png + applet_dual_joycon_dark.png + applet_dual_joycon_midnight.png + applet_handheld.png + applet_handheld_dark.png + applet_handheld_midnight.png + applet_pro_controller.png + applet_pro_controller_dark.png + applet_pro_controller_midnight.png + applet_single_joycon_left.png + applet_single_joycon_left_dark.png + applet_single_joycon_left_midnight.png + applet_single_joycon_right.png + applet_single_joycon_right_dark.png + applet_single_joycon_right_midnight.png + applet_dual_joycon_disabled.png + applet_dual_joycon_dark_disabled.png + applet_dual_joycon_midnight_disabled.png + applet_handheld_disabled.png + applet_handheld_dark_disabled.png + applet_handheld_midnight_disabled.png + applet_pro_controller_disabled.png + applet_pro_controller_dark_disabled.png + applet_pro_controller_midnight_disabled.png + applet_single_joycon_left_disabled.png + applet_single_joycon_left_dark_disabled.png + applet_single_joycon_left_midnight_disabled.png + applet_single_joycon_right_disabled.png + applet_single_joycon_right_dark_disabled.png + applet_single_joycon_right_midnight_disabled.png 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 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("hid") + ->GetAppletResource() + ->GetController(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 + +#include "common/common_types.h" + +namespace Core::Frontend { + +using BorderColor = std::array; + +struct ControllerParameters { + s8 min_players{}; + s8 max_players{}; + bool keep_controllers_connected{}; + bool enable_single_mode{}; + bool enable_border_color{}; + std::vector 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 callback, + ControllerParameters parameters) const = 0; +}; + +class DefaultControllerApplet final : public ControllerApplet { +public: + ~DefaultControllerApplet() override; + + void ReconfigureControllers(std::function 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 #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(); + if (frontend.controller == nullptr) { + frontend.controller = std::make_unique(); + } + + if (frontend.e_commerce == nullptr) { + frontend.e_commerce = std::make_unique(); } if (frontend.error == nullptr) { frontend.error = std::make_unique(); } + if (frontend.parental_controls == nullptr) { + frontend.parental_controls = + std::make_unique(); + } + if (frontend.photo_viewer == nullptr) { frontend.photo_viewer = std::make_unique(); } @@ -211,10 +238,6 @@ void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.web_browser == nullptr) { frontend.web_browser = std::make_unique(); } - - if (frontend.e_commerce == nullptr) { - frontend.e_commerce = std::make_unique(); - } } void AppletManager::ClearAll() { @@ -225,6 +248,8 @@ std::shared_ptr AppletManager::GetApplet(AppletId id) const { switch (id) { case AppletId::Auth: return std::make_shared(system, *frontend.parental_controls); + case AppletId::Controller: + return std::make_shared(system, *frontend.controller); case AppletId::Error: return std::make_shared(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; + using ControllerApplet = std::unique_ptr; + using ECommerceApplet = std::unique_ptr; using ErrorApplet = std::unique_ptr; + using ParentalControlsApplet = std::unique_ptr; using PhotoViewer = std::unique_ptr; using ProfileSelect = std::unique_ptr; using SoftwareKeyboard = std::unique_ptr; using WebBrowser = std::unique_ptr; - using ECommerceApplet = std::unique_ptr; 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 + +#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 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( + 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( + 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(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(sizeof(ControllerSupportResultInfo)); + std::memcpy(out_data.data(), &result_info, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared(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 + +#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; + +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 identification_colors{}; + bool enable_explain_text{}; + std::array, 4> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgOld) == 0x21C, + "ControllerSupportArgOld has incorrect size."); + +// LibraryAppletVersion 0x7 +struct ControllerSupportArgNew { + ControllerSupportArgHeader header{}; + std::array identification_colors{}; + bool enable_explain_text{}; + std::array, 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 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& controller_ids, last_processed_vibration = vibrations.back(); } +Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { + return last_processed_vibration; +} + std::shared_ptr 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& controller_ids, const std::vector& vibrations); - std::shared_ptr GetStyleSetChangedEvent(u32 npad_id) const; Vibration GetLastVibration() const; + std::shared_ptr 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 + +#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, 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("hid") + ->GetAppletResource() + ->GetController(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()), + 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(&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(&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 callback, Core::Frontend::ControllerParameters parameters) const { + this->callback = std::move(callback); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard 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 +#include +#include +#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; + + // 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 player_widgets; + + // Groupboxes encapsulating the controller icons and LED patterns per player. + std::array player_groupboxes; + + // Icons for currently connected controllers/players. + std::array connected_controller_icons; + + // Labels that represent the player numbers in place of the controller icons. + std::array player_labels; + + // LED patterns for currently connected controllers/players. + std::array, 8> led_patterns_boxes; + + // Comboboxes with a list of emulated controllers per player. + std::array emulated_controllers; + + // Labels representing the number of connected controllers + // above the "Connected Controllers" checkboxes. + std::array connected_controller_labels; + + // Checkboxes representing the "Connected Controllers". + std::array 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 callback, + Core::Frontend::ControllerParameters parameters) const override; + +signals: + void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const; + +private: + void MainWindowReconfigureFinished(); + + mutable std::function 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 @@ + + + QtControllerSelectorDialog + + + + 0 + 0 + 839 + 630 + + + + Controller Applet + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + + + 0 + + + 10 + + + 0 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 75 + true + + + + Supported Controller Types: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + + + + 0 + 0 + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 16 + + + 14 + + + 16 + + + + + + 75 + true + + + + Players: + + + Qt::AlignCenter + + + false + + + + + + + + 14 + + + + 1 - 8 + + + Qt::AlignCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P4 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P2 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P1 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + Handheld + + + + + + + + + Use Current Config + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P3 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + false + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P7 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P8 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P5 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P6 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + + + + + + 15 + + + 15 + + + 8 + + + 15 + + + 15 + + + + + + 16777215 + 16777215 + + + + Console Mode + + + + 6 + + + 6 + + + 6 + + + 3 + + + 6 + + + + + Docked + + + true + + + + + + + Undocked + + + + + + + + + + Vibration + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 65 + 0 + + + + + 65 + 16777215 + + + + % + + + 1 + + + 200 + + + 100 + + + + + + + + + + Motion + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 57 + 0 + + + + + 55 + 16777215 + + + + min-width: 55px; + + + Configure + + + + + + + + + + Input Config + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 65 + 16777215 + + + + min-width: 55px; + + + Open + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 3 + + + + + + + + + + + + Controllers + + + + + + + + + + + + + + 1 + + + Qt::AlignCenter + + + + + + + + + + + + + + Qt::LeftToRight + + + false + + + + + + + 2 + + + Qt::AlignCenter + + + + + + + 4 + + + Qt::AlignCenter + + + + + + + 3 + + + Qt::AlignCenter + + + + + + + Connected + + + + + + + + + + + + + + 5 + + + Qt::AlignCenter + + + + + + + + + + + + + + 7 + + + Qt::AlignCenter + + + + + + + + + + + + + + 6 + + + Qt::AlignCenter + + + + + + + 8 + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + false + + + QDialogButtonBox::Ok + + + + + + + + + + + + + + + buttonBox + accepted() + QtControllerSelectorDialog + accept() + + + 20 + 20 + + + 20 + 20 + + + + + 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 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(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()), + 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 +#include +#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; + + 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 @@ + + + ConfigureInputDialog + + + + 0 + 0 + 70 + 540 + + + + Configure Input + + + + 2 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + + + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ConfigureInputDialog + accept() + + + 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(*this), // - nullptr, // Photo Viewer - std::make_unique(*this), // - std::make_unique(*this), // - std::make_unique(*this), // - nullptr, // E-Commerce + std::make_unique(*this), // Controller Selector + nullptr, // E-Commerce + std::make_unique(*this), // Error Display + nullptr, // Parental Controls + nullptr, // Photo Viewer + std::make_unique(*this), // Profile Selector + std::make_unique(*this), // Software Keyboard + std::make_unique(*this), // Web Browser }); system.RegisterHostThread(); @@ -2047,6 +2065,7 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); + qRegisterMetaType("Core::Frontend::ControllerParameters"); qRegisterMetaType( "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType("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 uuid); + void SoftwareKeyboardFinishedText(std::optional 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; +using ExplainText = std::array; struct ControllerParameters { s8 min_players{}; @@ -19,6 +20,8 @@ struct ControllerParameters { bool enable_single_mode{}; bool enable_border_color{}; std::vector border_colors{}; + bool enable_explain_text{}; + std::vector 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 identification_colors) { + ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, + std::vector identification_colors, std::vector 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( controller_user_arg_old.identification_colors.begin(), - controller_user_arg_old.identification_colors.end())); + controller_user_arg_old.identification_colors.end()), + std::vector(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( controller_user_arg_new.identification_colors.begin(), - controller_user_arg_new.identification_colors.end())); + controller_user_arg_new.identification_colors.end()), + std::vector(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; +using ExplainText = std::array; enum class LibraryAppletVersion : u32_le { Version3 = 0x3, // 1.0.0 - 2.3.0 @@ -65,7 +66,7 @@ struct ControllerSupportArgOld { ControllerSupportArgHeader header{}; std::array identification_colors{}; bool enable_explain_text{}; - std::array, 4> explain_text{}; + std::array explain_text{}; }; static_assert(sizeof(ControllerSupportArgOld) == 0x21C, "ControllerSupportArgOld has incorrect size."); @@ -75,7 +76,7 @@ struct ControllerSupportArgNew { ControllerSupportArgHeader header{}; std::array identification_colors{}; bool enable_explain_text{}; - std::array, 8> explain_text{}; + std::array 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 +#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, 8> led_patterns_boxes; + // Labels representing additional information known as "Explain Text" per player. + std::array explain_text_labels; + // Comboboxes with a list of emulated controllers per player. std::array 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 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -635,13 +665,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -806,13 +866,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -1086,13 +1176,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -1296,13 +1416,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -1463,13 +1613,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -1634,13 +1814,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -1801,13 +2011,43 @@ - + 0 10 + + + 150 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::AlignCenter + + + + @@ -2395,7 +2635,7 @@ - false + true QDialogButtonBox::Ok 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 player_widgets; + std::array player_widgets; // Groupboxes encapsulating the controller icons and LED patterns per player. - std::array player_groupboxes; + std::array player_groupboxes; // Icons for currently connected controllers/players. - std::array connected_controller_icons; + std::array connected_controller_icons; // Labels that represent the player numbers in place of the controller icons. - std::array player_labels; + std::array player_labels; // LED patterns for currently connected controllers/players. - std::array, 8> led_patterns_boxes; + std::array, NUM_PLAYERS> led_patterns_boxes; // Labels representing additional information known as "Explain Text" per player. - std::array explain_text_labels; + std::array explain_text_labels; // Comboboxes with a list of emulated controllers per player. - std::array emulated_controllers; + std::array emulated_controllers; // Labels representing the number of connected controllers // above the "Connected Controllers" checkboxes. - std::array connected_controller_labels; + std::array connected_controller_labels; // Checkboxes representing the "Connected Controllers". - std::array connected_controller_checkboxes; + std::array 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; // 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 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 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 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()), 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 +#include +#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 +#include +#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 +#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, 8> led_patterns = {{ {1, 0, 0, 0}, {1, 1, 0, 0}, @@ -26,8 +29,6 @@ constexpr std::array, 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 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( 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(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(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 @@ single_joycon_right_vertical.png single_joycon_right_vertical_dark.png single_joycon_right_vertical_midnight.png - applet_dual_joycon.png + applet_dual_joycon.png applet_dual_joycon_dark.png applet_dual_joycon_midnight.png - applet_handheld.png + applet_handheld.png applet_handheld_dark.png applet_handheld_midnight.png applet_pro_controller.png - applet_pro_controller_dark.png - applet_pro_controller_midnight.png + applet_pro_controller_dark.png + applet_pro_controller_midnight.png applet_single_joycon_left.png - applet_single_joycon_left_dark.png - applet_single_joycon_left_midnight.png + applet_single_joycon_left_dark.png + applet_single_joycon_left_midnight.png applet_single_joycon_right.png applet_single_joycon_right_dark.png applet_single_joycon_right_midnight.png applet_dual_joycon_disabled.png applet_dual_joycon_dark_disabled.png applet_dual_joycon_midnight_disabled.png - applet_handheld_disabled.png + applet_handheld_disabled.png applet_handheld_dark_disabled.png applet_handheld_midnight_disabled.png - applet_pro_controller_disabled.png + applet_pro_controller_disabled.png applet_pro_controller_dark_disabled.png applet_pro_controller_midnight_disabled.png - applet_single_joycon_left_disabled.png + applet_single_joycon_left_disabled.png applet_single_joycon_left_dark_disabled.png applet_single_joycon_left_midnight_disabled.png - applet_single_joycon_right_disabled.png + applet_single_joycon_right_disabled.png applet_single_joycon_right_dark_disabled.png applet_single_joycon_right_midnight_disabled.png 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 @@ rc/radio_unchecked.png - style.qss + style.qss 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 @@ rc/window_undock_pressed@2x.png - style.qss + style.qss 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 {