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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 AR{#J2 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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 U%ss~#7#J8lUHx3vIVCg!0HqJK3jhEB literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_disabled.png b/dist/icons/controller/applet_dual_joycon_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..43950618da140121d2ef0fe768c4f8c2eb7e4a48 GIT binary patch literal 3314 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 HDWM4fTCsnZ literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_dual_joycon_midnight.png b/dist/icons/controller/applet_dual_joycon_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..c7edea8a3529455236af85e4c4c067f8354fa671 GIT binary patch literal 3549 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 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.png b/dist/icons/controller/applet_handheld.png new file mode 100644 index 0000000000000000000000000000000000000000..7f8dd22275b8dc5a56128f43a458a4e1ff9f58fd GIT binary patch literal 1671 zcmeAS@N?(olHy`uVBq!ia0y~yU~mIr4mJh`hC;nZEes3{EX7WqAsieW95oy%9SjT% zoCO|{#S9F5M?jcysy3fA0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}iES@foAr*7p-mDG?36)|0@P2Rgdp(8Shj#5&Q1*|EJAIg)LsMZ&#-yi8U#BNc z?!O=@xpGeCu{pmM?>{5e`?mF}$28X3VtnIC?AtGX8 zF3+AlYcu9Ce%M%AT6+JJ&H3)5la@!9T3O89)876iA!C-4SCNs9*k{hJMJtYWi?83c zIdAjLIajY<4eyt=zgPM6Qt#HebDvkO{PE+*O-)VB`)_V+bXJ{yx;EDK$BBuH!>_)& zD)`&}r$}?N+4-Jlin9$5iyxhB{_w@cLz$C75;T+PvzxL*XG%ZpEG$K zZrt_YLGA7B(~s?#HRaQ%q8Vq!T-@9^7!I7YRKL>uw|39udXxQfvla0I3|E-uc@z$b6_=F`*X z=U;k7M@5~IJLMs_Y0A&f-kUZT$EHQcZ{e$}GiG4;`jvOW>C}#{E+&SSwl=TNpTpTM z7#bQbEWejxB31bJm;AePZ$pcpooNh_>X1CB-OZca;BNt-Ee>75wvnc>FI6(4i-87cbwDL$2bN|VA{2)%UX>XV%d^^pjh>aMd<3@6DU{+O)L4e;!}gdeMTR`)JSe>+74ZW*u#8ZdXW% zunAwm#?U7BI%jKCqP!fVZ?Bo9n_I$W-b&3~)jvC2UhFGm?nx+#K4idCQ{y7Q8M#5n z`j*)>OA`|jPfyMr1q)@%j&nGru`AXF&MKd@JF@DRl8UuE_>uKsO1c+hdla($iVEk&OYqAg1S08 zBO@b2-v24PcI{elef#2-D>;jH@@P%vs`{&HVq*GuQQZ5hj5|(Wn7})ynVBI#q&4w3 zhYa8Gin)voOM^Pq!=D_yeqFrR_v{N}i4R9M9T$|7YdfRrz2Iur-pTiRHyIgUym6!D z-8(shH3E_^Gli%1eKb{5PuFC4ur+r5VJnM{4I2WUJYWB%CW(KdhsuqQr%q?&WF7M9 zWZ@RmnGrC5(~q?D^n)31+>??P9e1kg-oM|zsJJNR+A^OrUAMc}-ppZR;AnOFuKIbA zSFY-=UAs0cE8rF3Iy&Xolv*B!E!$pwIMmjAWB2>m?d)Hs&CkjThz)K&*2Xrybh>rC zyuAF0r$rwB5C5MkWdfsctgPSHT$VY`TcL4! z%fZ4s47o-#&)r+EeAwXf=QBrG-3tD1oZ+J;Tm0+G%dRb3wlGcfaJjp7afHr0MgilU zwu*``&sKgG-}kX0^6nu!n>R%>CwLq&f0(56`1{SbHMO<1tcx#Z@VUFY%ZIGK>iYl7 z)1aAWo^Cv??D*$>-sP;VqN%B=@x}}bE@qeoiyVFW^r`62pFi(@jZmn{6N$Qg>wy1e z<*2S@3+71IpSLrc-Mf0#s$WI#@9jN$V2ShJNs}fme|C2E_cv=IH!qUmJN{#1SKNC0 zQKX|i5t*V5XjrtfO*5F(eiD}`&<1P13# z`%}*CIFcIqC*Rocck;cA0!ocNX$L!+b=w%B&o5oh=a4|WF7P98N?7md} z{-48)3r4Y9)^7OxbN%=7cW=MXnKQ4tn%^N%#1$F&iat5WuR1$Udxm8GEi>DDB8NB4 zao|v%>2pkY(v$3OReQxvXT6YptbgI+#fca0*T0vvYbyWU)>hH?c#o&2XYaz-hZb+X zohz>)(skv;i4*C!Z&`7rM5^WWd+)k=vGn`9yZh~ID>i(ZJ#iZg!?wJ;SploAgj%i+ zJ7*@<%h}Q+ssH8scX{=e6SaT;zSjR=Cb~(beTR2{y8(;$WpQV5;b88DjIFmy`LDSK zPO;jpBU}3Q>)UgT)i?Lm{;u+Hb#1*UV*mKYEb(ylk2dRhN*czUWD5u<3?)-t1lkc0Cm*3ZWawTw9fB5s~ zXZcMuo}c^6b^d|EGc`4%7J(lwg+8|TQYIZgzixS}j`xnz-PI;@&OdL~InBUP{OgNk zMsm`G>9dWW$v6ruy0$LXc>S(l7xMJ9m7N!daLsZMKY!{J3qwh1BvV~wUEaBGbFJ_H zed1aA%=6aP*MH5w*M~|PMCZlnO=)6a$jHoXHH+%by!?oPp-JJzrp=qT_~fj~JJzgT zxi`*2rZ3?+-)f1qVoeGQHgD#(pYuzyJ-e@GI~Ou8e|=8rUV=kqBxhS&+lQO;^c^`iEKM?IC^+Mh+@CJ6=tZ-J z7XyQ&QH);sj799?=iLwFZEoy;z{tRJSi#JXeXgFEvbk{Hl8tV{t!H|kKa~5T(A3`$ zm+z3}Ra7yF{pKOo&l?}l-EL^`;J`oTsTYoBY+SiTgu#KM@%rn(=?M%AmMoFbT&Y=L zCF9}i+gecnbY54)G}+kj7&-G}_GJZ+{XL?#XqcRw$D}q{P=9SDtyXM&Q>Ti91n^*cwOTF{*W#iqv@2h{Fl~{eX%kjMcn+)HIkW~{{c-ZHk zZ~x2A_u!PMaYWxyZE3EFvo~jLImg(b(PdHeMdFH<>Zwz4t+$^x9ZfpeDSUt7hJ@N1 zhuA0YTqyYbeEgxe@1heV*pBztcR!7s`QYd8?F9@QZsug<5&&wn=N#C89qFG*T3LFrOLUPy`ncy zJI}XpQBqR+aP3>}jiDx+4=fDYQZav+IpQ9IusQ>_}^5= zJbCI&#)OKi(^NQl9+$h%7kc&T{3Ew$Ycnx1hs7W34{+be?mo&??5ynf_ebyr=Cd9~-Q>1d^HF78 zpN7}s+gg6h7YD8lQ}55Vdnw?Q(w@A)b8VkY?aZ?VM_zrcy8Wi?`NKz#7Tqd%wpgQU d5i)9eoFBURXIS&^IiTu;!PC{xWt~$(695-c_oV;; 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 zcmeAS@N?(olHy`uVBq!ia0y~yU~mIr4mJh`hC;nZEes3{EX7WqAsieW95oy%9SjT% zoCO|{#S9F5M?jcysy3fA0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa~7=RI87fhNw=LqYX#+W6mCe5C0U3Yq# zZu=d+7Zr}bst;~TJ-u(DSLV`Hb+?~?jxnqM_vcv-i`kVK|Kcv(xN(D{s*OWjU8r;E zO^#O;3+{_DZ%8@0e!0ZtD$x(CW=V_d)oeKTbVu8qx^Nzc$eT^(u0K>2l+LX>+AUsx z7bMQ8%C=zT%1q4T;bC7!|eaX0Cj5<7!dnx2q&f zcsjKFdBa2BR3$OYnmt>Yr#Q6cjQ7cHZ)1+XmdzLHT(59EJu>&+$;s;VHdS9H7X{o}EC zbK2d~ZBKL8{r{f+|4Z-ug0!uRf{i;=8?_*!1`F`ETNL%^vo&T)$L!_3Bk#cRx)Xo_lLQ%vjJEvF6u$qtz>3)-d z$Hub1(zcR*>S}Up&9>REus!=yvi9+njXT5rW|?U2U#(U5daZe3g7gE~7iYgsuh`<1 zQy)<6$-}_f_^2ju?M4;TtI3m$;xA6!tGblGtnKx0el%S)d`mF{Hs zZ2s1zz09xn>DIS(j`|V}=X(OCCOxpOJ-hks&Ge&>U)!;)Aw!PZN`16dwZTUTO?1X|PG|$C~4NI^9=`Eqz|% z?PIfVpWO1?MH=SYw)M=2h>rg4X~v|jt$ka4k?idCkrSs+H!pmBZSBdNAFq0M?b;Q_ z6CAi~(XYMVR7++o{@2lS{BiuTq|n>*YQN1~zezGeLnO-j%HBua>hT-y7@k`yy(o0Q zby3cRQ`^;Lj_*7!@tR%cohW}+v9=7K^?}>Ay|ST(tc?%)bL&6$H935odXsO>nl)<} zzvwx*Mr=trx#@lS?et|bQze@H80RXzikX%B{Ca%--;Mx{Ev{2Vd5YHG*!EWDcKnx` z#{EAg$a2`3m%qC+G4{Ci^q@w2i`WNdzCXQW{k`XIi;5Td)qQQ%;`2*_-kf$Tc**F$;UrVlkJhxP&fBjGK4J!=<{!Hz#v2j|Pq7$}LY15-?2i}I?i3%yH%YVFgpWVj4uIq3Ad(fD}a#j58su>w(p&Kt=v6?*P=-$5X zW(rEtEPof@W=!om|NO0uV_xi+_KK}O)BquVntFEh2ZTjl=ijH~a; zD=qVmZE7|u%Q#Pp4@u zHWrUEul!Rxw^RN})PaSM@0zY~OyQX|cmF2iLl38AUjD?VGI{0P11pv8fGV+L-^ACm z>@_o1F6vt)C!+Y*?5gwc;GeF6wKig_>vk5W<*PLve4x4NmgX9v&LbShU%&mleX{N> zDJi?Pt5;{o?%GnbjU$_X{(>J13@5K*l+e7iY}MTVURQ2d7~PEKzW#M_TC<}{P#pJ< z##~|bd7l>_I&^5tZM#iHi?nX1M2j9aSS#_OY@NkZi)UWj&x`V(?rQw9GpV+TFI0xF zJ@)SZw%jC!qM{sEdH7?wD!=W2CY|MmJY&TvBpRaI5} zWuY58csxYsPv~U)P_>skw9-G+?$p_{vYX0I7J9n69<2zqRovb^c}~iV5BA?m6m?E5 TJ}bw-z`)??>gTe~DWM4fCodF= 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 zcmeAS@N?(olHy`uVBq!ia0y~yU~mIr4mJh`hC;nZEes3{EX7WqAsieW95oy%9SjT% zoCO|{#S9F5M?jcysy3fA0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa|nTAnVBAr*7p&Z+GQxhmN{|N9x8%^PZTZX|5WABOc7IycSe&<>5Zl;|wQ`z@g}*u;Tg+$w$K%7gkK3kpXKAncy(`S2Q?Ge^;d6b*wvJuXb6V$% z#L#uj>ty8?ec!Aw`PY%A;|UBkPyaJKW14y(_<-7j-rU>UYCoK-mj7!!oqvj)t%?$R z;5t9s?1|2DiRsR{Po-BrXTL9zp8H()*oidrHk-xAHf-<5>GrIl$CB>GH?F!7qtiW3+`UEa@#Kj z9+hRPGQZmvMT@SD6XyAowB^9Uf)I6&J$sJ-s_Qy2-RQz6`Pmm-`-BA^Fck0#l+5v! z?p+%oU{vufqGPq%lh`=Ppkf5%$YJ&C)L%2Q?3 zxzFtVr*+@@eeDzpT|>{PLvuyD{#=qe&$Rw))#{Ma(?=&23EmU^5qT>p^xC@KxXAeU zt?V~AY`AiAd47qTv|GP7_c%9e>Y?TX;&yMX)P-ihy%HwJn8Cvq$LnfUueWXgsqR-v zg4OROvj1<`xNep0;nm^Hq6c~%e$PrcV86`DSViRB9@!_0Iytwx{#|iw*6l_7Ygew8 z`oaEM%yCT+N=kNxnF4`M@BX-BFKs9R4=@fbtG)Ui~z~f2ztS z6hmwIr+%OO{FHhn|6Hr|^`-|{EbdL#G1&L-`jJhye^k}a_7z;Ebs)N-`Sx}j&v`M0 z-WJy+xQ_dMUHxzM%5_XEnAtIr6KcIIpOr_GgFq`S{3j~>V2y@?~fUQdws-y z3VdLWE^uF6dtjl=bxWT8_vdqLJu1e)z2=qp-+vF!nP>Z^^5n2?W1L(lZ1S%7=(gp% z4@c$Yty%l$V{7V9AD#pJ4T}%t9(epXcIRV@Yb)1RSug!Bo#GOl6(_yhxo4fZQh&^k zgIeq*kEJ$FZxHyvwZZ;)Y1)yFBOM0vGuGdj-58_JmM}w`O^o6B@r7}73XaPxPTMGY z_azJS`{mPk&6ZC4(B`_Z;ESgIrmImqFaA*bYbDZ`$GbK6$1RJDnBLvZkJ#=!w0XW_ zmBg92mKlz5k{=?QXDsA?%Y5Ke+~xfjR<8e(U9a>nG=X)|!wP6?SYU&mFxdEp}*-+5IRW z`b*fyQzwsIo%+5nY~Hm8rrTY=w;$RVd{b}axT0?{?NQN z+^6P*gzvGseQ@nJ-L1!dXY4A{U$8mttdt@DE;Wf2r?$=Tej;)>_T%Z4i=x)0{k^+- z7O%P_)VY$;&hDP<{HbfXHv3!>tuCA$ey%*gWc_Um?jJY0KUw$RSh}M-X?mN1sYsvh ziCw!6{`mWG*{xTJ#YY?VuU@_S$&r&ke8qQ%UR;?R*<^4{Pq4x_>bA95RQ9&%1`n4d ze0cb-#Jnf-=aWx?fsvOY!YgIU*X>}5VA^v-sLyV3LHx2tqe#0>7p2QpSIQZl)?E$= z{>$z%|E=mqWuZ==(+m0*&+IzMv}n=nxnE!R8$RhWXAb|O{w49kVaa2bx9;AxP0CUf zJ$}<*Yx||QtNtuoskh-gcf#Z4**-77e6@XLEx=X2Eo%P02O;6}Wpod?uwY>eYcR6-9BN#pUZjrpTEuZxNd7HD{JiIG_5h$a0EL#BkM>IkmRA^^^%ud!XK|pK}z;B7<|kYTkKz@8yoq$D*Acp3|FPZU48GRyu+2=0ZH`bgip}KVPx!D!FVdQ&MBb@09!CO>;M1& literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_midnight.png b/dist/icons/controller/applet_handheld_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..7188b915456066975b1c2e401645d9fa7cee6f77 GIT binary patch literal 1644 zcmeAS@N?(olHy`uVBq!ia0y~yU~mIr4mJh`hC;nZEes3{EX7WqAsieW95oy%9SjT% zoCO|{#S9F5M?jcysy3fA0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa~NFFjoxLn`LHy;#&AIIn2+?4Y>`Q)az@{@CCCbsz;%~9BAX6Tsu<5};Xm7g}9VBxC0yRE$5 zK;pux=$P9F&#AwZx7~l=_V3){{QP&8;tMpo79k@sE~C%h@7flfeUT%RQT`6j z4(%x6x>Gru&7;puTfIx;Y~xN@lWnChdX7B|=u|n7wE4EH)5H_=X3xC$Vz=U-+O~WD z&WjX9u8Lagll9sphpi$>YJNfK*Kg9teUjtD!)4abIHk%kVQZm##gwSnN&C&r&3xxC z5awXnY_;d=z0xKFp7WpoY%V>!wqR$Bo!q6zi4ts?zm_VrO!;i%FEsr~-Ym)O#dBV} z)fS(hx$6F|UbV;XPN$}pdp`TMsN;(2u1&VqT$4qORvrmHE}pecx+qNVdTsFzk-O!; zYK&*qN3JmwTL0!=+beBzUd}>8t4<3BuAMny=T5ws@$$EPeO{jYn|p^cLQd(IiHfOI z%$o98z23Dj)3V|N595Op%k-n1iht^V8gC9;ewkB_fq&l5mtQRBtl9Ixtn!pY>z~%# z*^fenu0GGX_iAdF!qlLLnbW$>?06YgtlIhPqYeja#pfpzAL+{%E&8)8;rTuDoH>V+ zbE7|hRuJoEWcYe^Ek}^1pr{}l14r2ErcXuTnln#y6udptd`$g}gw($GZ+HFGGKt`A z_34V1y_@lNUvuzt{j*)iT{0I%P7`hMF`cu!LNYa7``}H>l%>lP-=90H(6Xa+kH8l` z9;dZjEa}^wr3ITVZY=z;hx?QRYeb_$538b%LFLjZ^DWt%ADS^d5K4FVN|M=hDfo{S zL;jAU&5W^on-d-!`W$V3C~51%3m1(Hc$#Bde=;cQ7*EaF7LmBwdKrJ2Ma+f>ojT1s zx0#q)4EXLgPe^5CSkurLqt)u8#rpgCVlL~cGEy=vqNk@XY}?#iY$3S1+pVhWp8eG7 z$XRxRo*xS(BxRjHe&q6CYN^OOUB%Fl@a_o1fz#I96K9F=ci-&#GDSt>Nznc9x5`Qj z1(tCdi*Q-ygh@ZMyK-)pgcHZ6OJetIpE^D&i85$$5S4f#mTddv|N0|t?v**|&&*`3 zg4&vox*KL0RqoZOFaBtAYj^(Rb%i%{pOqb*u{NyZXi`tlECG$_zn3`FZzz`G6O(rh z_Lh!N>~2cGrtp8|)ysuq3>NLoOV!UgDe*a6Y};xuQ}>0e!K0e5S0hhypWM1D?aDRt z#2?=i1(@cn+tZ;Qmg=-Cqv+%bh64*e%DwCNO~^>$anw?sb|mBCjaz*iE(B~T`}SjV z-TIBU-xhqFd|Tayzrc^(qHlKJW`+e;R8rJXAcJ%0wGF#1CxjOvk zz9jjHdQmOgzwPbV^~_8oCFq6lqqL)U^Y)zO~ygC$L^@5 z`JUrqn4GQ7aQSs->#|ReL?wQ)Z%n-_#Zq~ruc*3@v0>XfxsvbqkLrh-#_3;(uVQbX zFl|oDYhGQs$8zd!Dk4cqHB3&|k}DiJc!M5B<=PqrO{?mu{xknbhO*$|-Y;2+|F$LO zo^yWObNZ#!$3WfBZKq#KC918tcFSFLQ_q1z_F9>83JeipA5Ppg-+VUe{I-&k=_*Iu z@>3(fHO}5$v@@qcQ6({Nw};BljNjht_x_*pwW{^-!Odpp3|IFGFzpPn;y9426Kv7m z{Qix}lnwLuR6mGFxv||oC4I@;pUds-^31Ah-Wtu1&Ck&L`QYte_Xvg+87pc-4;Y*a z`FqKhQEB1>NA{PG#gh-^9IW{rEy(bzgTe~DWM4fG`aqq literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_handheld_midnight_disabled.png b/dist/icons/controller/applet_handheld_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..6ccfc325365322b407d32606ffcf7a8c55610add GIT binary patch literal 2634 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 U?cddt3=9kmp00i_>zopr01c8=yZ`_I literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_dark.png b/dist/icons/controller/applet_pro_controller_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c122b7b11e1e2b89905eee54f8bf829332d9431d GIT binary patch literal 4236 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 SK$wAnfx*+&&t;ucLK6VcS?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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 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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 W`XuHO=9>%*3=E#GelF{r5}E*P8B4SP literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_pro_controller_midnight_disabled.png b/dist/icons/controller/applet_pro_controller_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2907f3be42b56b014388582d0bffca97e7e3a4df GIT binary patch literal 4459 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 F0st9u)d~Or literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_disabled.png b/dist/icons/controller/applet_single_joycon_left_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c0102dc20cdd77146c52493b7919918418f0536f GIT binary patch literal 2179 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_left_midnight.png b/dist/icons/controller/applet_single_joycon_left_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..56522bccb3b8b7739113310b03c748cb0ffe2e84 GIT binary patch literal 2065 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 I&MBb@04NXIOaK4? 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 zcmeAS@N?(olHy`uVBq!ia0y~yU~mIr4mJh`hC;nZEes3{EX7WqAsieW95oy%9SjT% zoCO|{#S9F5M?jcysy3fA0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVb02k3C%+Ln`LHy%`x0eO;3M!~d!8u8K;e2y~0(s5mI;m~CY?Q(M+IxkBj8j2q`} zyewH(x5C=j_^fyOt$klj=bhU!bDzrPT`PZI$UOem$C~-zL7jUHQ>C>8(9)a{t-+&qa3q&)vL>i(lW` z*!ugs{b#qNt*?Z(s!fjhysh=z`7_UU^Z)Dd6PM{fZn`w~MGO@g z$t#@O`L3QyF}irPSZ28>i^ijmD>l}JA3i_vVe>q*Ye$ckKP`$>XK+yx%&VxVnD*-3 zySSC>*X!3^I$9eXp)>8kL}m9YLS2hg{CCY*KRY{>b#Zd?qGU@;OC5%1^=ZL>>g?Uz z@{$V+3lHzeFyY#Kv*-2od?f*nCB5F&KhnGfSZ?H)t+<+X;mfq8XHWe2VX>>aN^#g3!-j&`H>cg> z4qceQxlYvFZ*ESz)4~Yx&6{KXpG(g7(GY3MED(4+`SORVy(?C%IB@(~f8eU1#OLSc zE-JQ>(`RF2e^_9VxLJ2`&T+nsD_MJf97??CWG>u!v|zzbNz?NokKY|p$!Kdl{7~TT zjIJ)OfGI4ePoK`n$q_m0uIOs}?bw;KXBDOV59DoUX885Hs_@qtNsBjS7kg*UnRCSb zt}TOV$ooc zFS#$*&)(^x^t$vSL(7_U+XZXZ@Lav>_Q9tA*Dk9kCn7VhXqlLptm&4OkYRE4+?cYp zDfR#DU?U!8hKPuWhJy(QdZo<^|NL0J=)APF^x>)V>{P-kH|R7!KHj%z+cvTMIhKC| z=G=dOeChswym|fn3>U8KV0XWLg#QWqM;rZsAT3YsCIPk&5xn=bb8|0bZGF(>xWP#B zo851nBex3M4>wK~70;L@cy_YSQh`pl#Bz(vcB_}Km(R`3op3sG$6dKE-(p>JbKmaT zvG3pN6)RRW*&Jt_x=Li)Oi2kHF^S$|yw@Ln{@Jv~=iNf#&KBVV$Lb3tZ|HvFj69vT z{qWP0#OQrL7*C&yICCyS!Lzfzqxqmh(nX1|@bHOVOAl<)X<8^yF)#kQ5(|?IQ;b0V z-k;7EbNqb#{0^pV&fjI&=%6seN6pscc;RJ+Z~41c=>{y*ZJr<^@W3hJH?Iss(nkp% zHf0m3sW1Ng`BU>mTjW8d+{zHHvfa$BtgMS_g99fD@f?2i_kNs1VWFTzTU*@v`&*99 zym`~ZMsD_mGiT1+IaSr#@3H#of)zVhnkIduV$^W7icv%T>0l=&L{i5^RyJr}>Kw>`TfD>qlyrvBfb z7Qf}e>HChjA1;)s``>P_B-fvuStzL4$-<-SY$M37JJDhJ<;GOWKR?#WH;PuSUi$cJ z)y1EQ;&b%GKYjl6@lv<`f@RNUu6h0X?c1$9GupP7mX}`h^iiAq{@h&a>6^}d;t=6t z^_!c~9&_$c!kV^KOIQ^38f2qPSFKv5Fxk-J@2{0>%vc%h-t6L;>(_tkl$J|!VM**i zj@7GIM?Mu`VchO^rswv{Jx@OWWC~*7Xm#5D`qeAHX;Y@uJiX5EwN!|Ace$bnkCW0w zC%x&8i?1FESb1DJ-LL+hcRS38(R$RPO=D4cej|Yn`zx~$t^=rv~UIkX04TcN_pP!v& zTpKn!-#}uP6Em1!!!`P5c|L*|lgvh?em_?x@H}&cE*tv9_N-@A}@? zx28NNC+FS4!i&+r7cN}rKj~!3egzKKqeqXrO-tSWq3r)jFSW@p-oKy!|F^xB#`M#+ zmHAnZtQRj{ygB=^nXUJ^xz^Wj-MaOwcBu9!|{5%gd{) zsHn&&DY=qrBK18&XIde{pULa`QR-8UCWVgtMfJ@0Ux_RI)!Ma|fq{X+)78&qol`;+ E0D!Iv9RL6T literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_dark.png b/dist/icons/controller/applet_single_joycon_right_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..af26cce4bb2cf476ac9b68cbda6b4bf494dd90e6 GIT binary patch literal 2146 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 literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_midnight.png b/dist/icons/controller/applet_single_joycon_right_midnight.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf70e21e1d6cb6b92858b5a5da818d1a6499d40 GIT binary patch literal 2150 zcmeAS@N?(olHy`uVBq!ia0y~yU~mIr4mJh`hC;nZEes3{EX7WqAsieW95oy%9SjT% zoCO|{#S9F5M?jcysy3fA0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVb02k3C%+Ln`LHy;+?Td|j6P!~4D8zAD$LZhP$BvygL%fQF=MYpY`g%Z*bB(KFOa zUDv!)3CVO?{mXrK=B+J;nR8!U@tb^Y<>hTzugna+7EWn#mlJT3-Lk|aM__@#iNi-& zju+O{{{6Q1o9*w#(IOp_Eqr%5R@>G8`txV^^MCI?zMogk8zABuh=RWUJmO=vVdJ}- zPU)SM>ze{6)Cet|w9=)4-^5UIldw+p`5AL2?$&W_janPHU*~?!Wl!~>i4Qu?D{Ei5 z{a#-9=$FdbhXd0zE?9p)se0tlajAR19$)=9UsIiX+RP=F7hM1EvNR;~)2x*zS?2mx zn@;uf{?2cBfN%57O|w!XUE_9svYEHcp*V7i%g=v`tcUAP=NQ`C9WCAOZZz)$^N!^P zXUf0r30Qb(lcm4iya`?@mf1dCK|AlJRd=3zZ^19CsJ169V09m(gO0B2y?=-Et7k4g zF6DMdZ~52%wmaEd?G2Rn{8}9@qa)Ea*Y3||RS~|$%g@g)?s;snRYh{=d7r>VA6a(U zziPA>m}BHOMI~m}wdUZpZ#&c{O+My(t0vt`jb-sdgEOse8Y0EllRpw!RK~-c!4KpP+fw-V%ydRg*z2y|n$_!?^6p$c9d0x8$mFfk8$NOOFWme(USKItQ;kJ> z;+!(=RnzoaPv;$8%q>4tML97oR^tBML>HyND>;=+Jj{RIEPej*a(G?y!iNdx4t=R& zFI=%t(#eF&bCN=u5ywYCo*(ZXeflbA^Yv@)jQ6Lq`iETP|tvDmcwq5lp_d|_s z2?s?iWBjaR{C-cdOut*h-OHvSA~R{F$TU5@@&9^UPQiG|f}yz#6lP+1J;#osR2K z&_A%?^1@9?7jKA4pMSl(dy9tBET1PQm^e0C@L1Wh)s(B-Ui&!B%w}h$GS?O<9$xl> zMNehs-QQg8bZ6!_K9T6P9x9g$cF%e7;?=Q5?sqQ#^LhNSVoIvvswpa&5(k=A`qx%@ zi%s>D7VdMID5#n+>9~ibwzJ4(0T#vQ6-HB?G;|z9Tm_UCJ1lmbz^^>}tltf#o;H>i zrNBR*j_V(Ky}OzF{~n%t1&iFomIxay;U2BvMHS2kzGUa{iOlSgcv@+2B34@UwA0#a z3szj1^7(gEFB@O9;gKGenF2g_CG_a9b>=b9%v*x3f%+pUd64qvolUl>dC! z2!Vj|`23&i*P32c*woWA#j&77WNUW#!A$*mMRh@H&g;H^u~PB;WU#xec74Wo^$oUlBdMBx^QnRz4_B!~YcSzg-!%OFTE1<;2IJFW1-a z|Cm*JwM#ri=KKEIaszK8#SiRm37cmu40-mQV}{t$Gtuq$UcY_$dD)`O!szhJyOXU? zbE@Aj`Q%a@>H0Ka;>j(`99CaE`|yr^D9_;o3$8!CThN+e@@@KI-ha`|%_nngewMhO zuKxS@c=5WfYtN2#hm}WiA2twM92b91b1MJUS2MX3Sz_;ov-8Q=9^~O=w^QPo_i2vR zrRw@STlPL_|6R8+uQ2jvId^Xx?`N+!-04N!H4|8V7L=$+RQDo23&*g$BHK}W$h-)q1AL-68-oA#GSyBuP3=E#GelF{r5}E)) CT>fAH literal 0 HcmV?d00001 diff --git a/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e6693b0270aec23df0150b4209f02db5db06c83a GIT binary patch literal 2611 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 mZN)21%h);3OfnDsXS}J(V;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 {