diff --git a/dist/icons/controller/applet_dual_joycon.png b/dist/icons/controller/applet_dual_joycon.png new file mode 100644 index 0000000000..32e0a04ae5 Binary files /dev/null and b/dist/icons/controller/applet_dual_joycon.png differ diff --git a/dist/icons/controller/applet_dual_joycon_dark.png b/dist/icons/controller/applet_dual_joycon_dark.png new file mode 100644 index 0000000000..6adc663561 Binary files /dev/null and b/dist/icons/controller/applet_dual_joycon_dark.png differ 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 0000000000..208603ee78 Binary files /dev/null and b/dist/icons/controller/applet_dual_joycon_dark_disabled.png differ 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 0000000000..43950618da Binary files /dev/null and b/dist/icons/controller/applet_dual_joycon_disabled.png differ 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 0000000000..c7edea8a35 Binary files /dev/null and b/dist/icons/controller/applet_dual_joycon_midnight.png differ 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 0000000000..ee1aafc858 Binary files /dev/null and b/dist/icons/controller/applet_dual_joycon_midnight_disabled.png differ diff --git a/dist/icons/controller/applet_handheld.png b/dist/icons/controller/applet_handheld.png new file mode 100644 index 0000000000..7f8dd22275 Binary files /dev/null and b/dist/icons/controller/applet_handheld.png differ diff --git a/dist/icons/controller/applet_handheld_dark.png b/dist/icons/controller/applet_handheld_dark.png new file mode 100644 index 0000000000..41f6d7aea9 Binary files /dev/null and b/dist/icons/controller/applet_handheld_dark.png differ 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 0000000000..5a136ddd1d Binary files /dev/null and b/dist/icons/controller/applet_handheld_dark_disabled.png differ diff --git a/dist/icons/controller/applet_handheld_disabled.png b/dist/icons/controller/applet_handheld_disabled.png new file mode 100644 index 0000000000..53f8ce7adc Binary files /dev/null and b/dist/icons/controller/applet_handheld_disabled.png differ diff --git a/dist/icons/controller/applet_handheld_midnight.png b/dist/icons/controller/applet_handheld_midnight.png new file mode 100644 index 0000000000..7188b91545 Binary files /dev/null and b/dist/icons/controller/applet_handheld_midnight.png differ 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 0000000000..6ccfc32536 Binary files /dev/null and b/dist/icons/controller/applet_handheld_midnight_disabled.png differ diff --git a/dist/icons/controller/applet_pro_controller.png b/dist/icons/controller/applet_pro_controller.png new file mode 100644 index 0000000000..e322588558 Binary files /dev/null and b/dist/icons/controller/applet_pro_controller.png differ 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 0000000000..c122b7b11e Binary files /dev/null and b/dist/icons/controller/applet_pro_controller_dark.png differ 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 0000000000..416e1e2fb0 Binary files /dev/null and b/dist/icons/controller/applet_pro_controller_dark_disabled.png differ 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 0000000000..72a549ea98 Binary files /dev/null and b/dist/icons/controller/applet_pro_controller_disabled.png differ diff --git a/dist/icons/controller/applet_pro_controller_midnight.png b/dist/icons/controller/applet_pro_controller_midnight.png new file mode 100644 index 0000000000..622e57fa13 Binary files /dev/null and b/dist/icons/controller/applet_pro_controller_midnight.png differ 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 0000000000..2907f3be42 Binary files /dev/null and b/dist/icons/controller/applet_pro_controller_midnight_disabled.png differ diff --git a/dist/icons/controller/applet_single_joycon_left.png b/dist/icons/controller/applet_single_joycon_left.png new file mode 100644 index 0000000000..47c44d43a7 Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_left.png differ diff --git a/dist/icons/controller/applet_single_joycon_left_dark.png b/dist/icons/controller/applet_single_joycon_left_dark.png new file mode 100644 index 0000000000..69530b69ce Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_left_dark.png differ diff --git a/dist/icons/controller/applet_single_joycon_left_dark_disabled.png b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png new file mode 100644 index 0000000000..cfe4b1475c Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png differ 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 0000000000..c0102dc20c Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_left_disabled.png differ 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 0000000000..56522bccb3 Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_left_midnight.png differ diff --git a/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png new file mode 100644 index 0000000000..62434c188d Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png differ 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 0000000000..b0d4fba906 Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_right.png differ 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 0000000000..af26cce4bb Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_right_dark.png differ diff --git a/dist/icons/controller/applet_single_joycon_right_dark_disabled.png b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png new file mode 100644 index 0000000000..b33efab42a Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png differ diff --git a/dist/icons/controller/applet_single_joycon_right_disabled.png b/dist/icons/controller/applet_single_joycon_right_disabled.png new file mode 100644 index 0000000000..01ca383226 Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_right_disabled.png differ 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 0000000000..5bf70e21e1 Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_right_midnight.png differ 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 0000000000..e6693b0270 Binary files /dev/null and b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png differ diff --git a/dist/icons/controller/controller.qrc b/dist/icons/controller/controller.qrc index f44725d8ff..76083a9ac1 100644 --- a/dist/icons/controller/controller.qrc +++ b/dist/icons/controller/controller.qrc @@ -21,5 +21,35 @@ single_joycon_right_vertical.png single_joycon_right_vertical_dark.png single_joycon_right_vertical_midnight.png + applet_dual_joycon.png + applet_dual_joycon_dark.png + applet_dual_joycon_midnight.png + applet_handheld.png + applet_handheld_dark.png + applet_handheld_midnight.png + applet_pro_controller.png + applet_pro_controller_dark.png + applet_pro_controller_midnight.png + applet_single_joycon_left.png + applet_single_joycon_left_dark.png + applet_single_joycon_left_midnight.png + applet_single_joycon_right.png + applet_single_joycon_right_dark.png + applet_single_joycon_right_midnight.png + applet_dual_joycon_disabled.png + applet_dual_joycon_dark_disabled.png + applet_dual_joycon_midnight_disabled.png + applet_handheld_disabled.png + applet_handheld_dark_disabled.png + applet_handheld_midnight_disabled.png + applet_pro_controller_disabled.png + applet_pro_controller_dark_disabled.png + applet_pro_controller_midnight_disabled.png + applet_single_joycon_left_disabled.png + applet_single_joycon_left_dark_disabled.png + applet_single_joycon_left_midnight_disabled.png + applet_single_joycon_right_disabled.png + applet_single_joycon_right_dark_disabled.png + applet_single_joycon_right_midnight_disabled.png diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 5da56520b0..5b05b436b2 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -41,6 +41,99 @@ QPushButton#buttonRefreshDevices { max-height: 20px; } +QWidget#bottomPerGameInput, +QWidget#topControllerApplet, +QWidget#bottomControllerApplet, +QGroupBox#groupPlayer1Connected:checked, +QGroupBox#groupPlayer2Connected:checked, +QGroupBox#groupPlayer3Connected:checked, +QGroupBox#groupPlayer4Connected:checked, +QGroupBox#groupPlayer5Connected:checked, +QGroupBox#groupPlayer6Connected:checked, +QGroupBox#groupPlayer7Connected:checked, +QGroupBox#groupPlayer8Connected:checked { + background-color: #f5f5f5; +} + +QWidget#topControllerApplet { + border-bottom: 1px solid #828790 +} + +QWidget#bottomPerGameInput, +QWidget#bottomControllerApplet { + border-top: 1px solid #828790 +} + +QWidget#topPerGameInput, +QWidget#middleControllerApplet { + background-color: #fff; +} + +QWidget#topPerGameInput QComboBox, +QWidget#middleControllerApplet QComboBox { + width: 123px; +} + +QWidget#connectedControllers { + background: transparent; +} + +QWidget#playersSupported, +QWidget#controllersSupported, +QWidget#controllerSupported1, +QWidget#controllerSupported2, +QWidget#controllerSupported3, +QWidget#controllerSupported4, +QWidget#controllerSupported5, +QWidget#controllerSupported6 { + border: none; + background: transparent; +} + +QGroupBox#groupPlayer1Connected, +QGroupBox#groupPlayer2Connected, +QGroupBox#groupPlayer3Connected, +QGroupBox#groupPlayer4Connected, +QGroupBox#groupPlayer5Connected, +QGroupBox#groupPlayer6Connected, +QGroupBox#groupPlayer7Connected, +QGroupBox#groupPlayer8Connected { + border: 1px solid #828790; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; +} + +QGroupBox#groupPlayer1Connected:unchecked, +QGroupBox#groupPlayer2Connected:unchecked, +QGroupBox#groupPlayer3Connected:unchecked, +QGroupBox#groupPlayer4Connected:unchecked, +QGroupBox#groupPlayer5Connected:unchecked, +QGroupBox#groupPlayer6Connected:unchecked, +QGroupBox#groupPlayer7Connected:unchecked, +QGroupBox#groupPlayer8Connected:unchecked { + border: 1px solid #d9d9d9; +} + +QGroupBox#groupPlayer1Connected::title, +QGroupBox#groupPlayer2Connected::title, +QGroupBox#groupPlayer3Connected::title, +QGroupBox#groupPlayer4Connected::title, +QGroupBox#groupPlayer5Connected::title, +QGroupBox#groupPlayer6Connected::title, +QGroupBox#groupPlayer7Connected::title, +QGroupBox#groupPlayer8Connected::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: 0px; + margin-right: -4px; + margin-bottom: 4px; +} + QCheckBox#checkboxPlayer1Connected, QCheckBox#checkboxPlayer2Connected, QCheckBox#checkboxPlayer3Connected, @@ -49,7 +142,43 @@ QCheckBox#checkboxPlayer5Connected, QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { - spacing: 0px; + spacing: 0px; +} + +QWidget#Player1LEDs QCheckBox, +QWidget#Player2LEDs QCheckBox, +QWidget#Player3LEDs QCheckBox, +QWidget#Player4LEDs QCheckBox, +QWidget#Player5LEDs QCheckBox, +QWidget#Player6LEDs QCheckBox, +QWidget#Player7LEDs QCheckBox, +QWidget#Player8LEDs QCheckBox { + spacing: 0px; +} + +QWidget#Player1LEDs QCheckBox::indicator, +QWidget#Player2LEDs QCheckBox::indicator, +QWidget#Player3LEDs QCheckBox::indicator, +QWidget#Player4LEDs QCheckBox::indicator, +QWidget#Player5LEDs QCheckBox::indicator, +QWidget#Player6LEDs QCheckBox::indicator, +QWidget#Player7LEDs QCheckBox::indicator, +QWidget#Player8LEDs QCheckBox::indicator { + width: 6px; + height: 6px; + margin-left: 0px; +} + +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { + width: 12px; + height: 12px; } QCheckBox#checkboxPlayer1Connected::indicator, @@ -60,10 +189,38 @@ QCheckBox#checkboxPlayer5Connected::indicator, QCheckBox#checkboxPlayer6Connected::indicator, QCheckBox#checkboxPlayer7Connected::indicator, QCheckBox#checkboxPlayer8Connected::indicator { - width: 14px; - height: 14px; + width: 14px; + height: 14px; } +QGroupBox#groupPlayer1Connected::indicator, +QGroupBox#groupPlayer2Connected::indicator, +QGroupBox#groupPlayer3Connected::indicator, +QGroupBox#groupPlayer4Connected::indicator, +QGroupBox#groupPlayer5Connected::indicator, +QGroupBox#groupPlayer6Connected::indicator, +QGroupBox#groupPlayer7Connected::indicator, +QGroupBox#groupPlayer8Connected::indicator { + width: 16px; + height: 16px; +} + +QWidget#Player1LEDs QCheckBox::indicator:checked, +QWidget#Player2LEDs QCheckBox::indicator:checked, +QWidget#Player3LEDs QCheckBox::indicator:checked, +QWidget#Player4LEDs QCheckBox::indicator:checked, +QWidget#Player5LEDs QCheckBox::indicator:checked, +QWidget#Player6LEDs QCheckBox::indicator:checked, +QWidget#Player7LEDs QCheckBox::indicator:checked, +QWidget#Player8LEDs QCheckBox::indicator:checked, +QGroupBox#groupPlayer1Connected::indicator:checked, +QGroupBox#groupPlayer2Connected::indicator:checked, +QGroupBox#groupPlayer3Connected::indicator:checked, +QGroupBox#groupPlayer4Connected::indicator:checked, +QGroupBox#groupPlayer5Connected::indicator:checked, +QGroupBox#groupPlayer6Connected::indicator:checked, +QGroupBox#groupPlayer7Connected::indicator:checked, +QGroupBox#groupPlayer8Connected::indicator:checked, QCheckBox#checkboxPlayer1Connected::indicator:checked, QCheckBox#checkboxPlayer2Connected::indicator:checked, QCheckBox#checkboxPlayer3Connected::indicator:checked, @@ -73,12 +230,28 @@ QCheckBox#checkboxPlayer6Connected::indicator:checked, QCheckBox#checkboxPlayer7Connected::indicator:checked, QCheckBox#checkboxPlayer8Connected::indicator:checked, QGroupBox#groupConnectedController::indicator:checked { - border-radius: 2px; - border: 1px solid black; - background: #39ff14; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: #39ff14; + image: none; } +QWidget#Player1LEDs QCheckBox::indicator:unchecked, +QWidget#Player2LEDs QCheckBox::indicator:unchecked, +QWidget#Player3LEDs QCheckBox::indicator:unchecked, +QWidget#Player4LEDs QCheckBox::indicator:unchecked, +QWidget#Player5LEDs QCheckBox::indicator:unchecked, +QWidget#Player6LEDs QCheckBox::indicator:unchecked, +QWidget#Player7LEDs QCheckBox::indicator:unchecked, +QWidget#Player8LEDs QCheckBox::indicator:unchecked, +QGroupBox#groupPlayer1Connected::indicator:unchecked, +QGroupBox#groupPlayer2Connected::indicator:unchecked, +QGroupBox#groupPlayer3Connected::indicator:unchecked, +QGroupBox#groupPlayer4Connected::indicator:unchecked, +QGroupBox#groupPlayer5Connected::indicator:unchecked, +QGroupBox#groupPlayer6Connected::indicator:unchecked, +QGroupBox#groupPlayer7Connected::indicator:unchecked, +QGroupBox#groupPlayer8Connected::indicator:unchecked, QCheckBox#checkboxPlayer1Connected::indicator:unchecked, QCheckBox#checkboxPlayer2Connected::indicator:unchecked, QCheckBox#checkboxPlayer3Connected::indicator:unchecked, @@ -88,8 +261,19 @@ QCheckBox#checkboxPlayer6Connected::indicator:unchecked, QCheckBox#checkboxPlayer7Connected::indicator:unchecked, QCheckBox#checkboxPlayer8Connected::indicator:unchecked, QGroupBox#groupConnectedController::indicator:unchecked { - border-radius: 2px; - border: 1px solid black; - background: transparent; - image: none; + border-radius: 2px; + border: 1px solid #929192; + background: transparent; + image: none; +} + +QWidget#controllerPlayer1, +QWidget#controllerPlayer2, +QWidget#controllerPlayer3, +QWidget#controllerPlayer4, +QWidget#controllerPlayer5, +QWidget#controllerPlayer6, +QWidget#controllerPlayer7, +QWidget#controllerPlayer8 { + background: transparent; } diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 16218f0c22..537558c1b6 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1284,59 +1284,6 @@ QPushButton#buttonRefreshDevices { padding: 0px 0px; } -QCheckBox#checkboxPlayer1Connected, -QCheckBox#checkboxPlayer2Connected, -QCheckBox#checkboxPlayer3Connected, -QCheckBox#checkboxPlayer4Connected, -QCheckBox#checkboxPlayer5Connected, -QCheckBox#checkboxPlayer6Connected, -QCheckBox#checkboxPlayer7Connected, -QCheckBox#checkboxPlayer8Connected { - spacing: 0px; -} - -QCheckBox#checkboxPlayer1Connected::indicator, -QCheckBox#checkboxPlayer2Connected::indicator, -QCheckBox#checkboxPlayer3Connected::indicator, -QCheckBox#checkboxPlayer4Connected::indicator, -QCheckBox#checkboxPlayer5Connected::indicator, -QCheckBox#checkboxPlayer6Connected::indicator, -QCheckBox#checkboxPlayer7Connected::indicator, -QCheckBox#checkboxPlayer8Connected::indicator { - width: 14px; - height: 14px; -} - -QCheckBox#checkboxPlayer1Connected::indicator:checked, -QCheckBox#checkboxPlayer2Connected::indicator:checked, -QCheckBox#checkboxPlayer3Connected::indicator:checked, -QCheckBox#checkboxPlayer4Connected::indicator:checked, -QCheckBox#checkboxPlayer5Connected::indicator:checked, -QCheckBox#checkboxPlayer6Connected::indicator:checked, -QCheckBox#checkboxPlayer7Connected::indicator:checked, -QCheckBox#checkboxPlayer8Connected::indicator:checked, -QGroupBox#groupConnectedController::indicator:checked { - border-radius: 2px; - border: 1px solid #929192; - background: #39ff14; - image: none; -} - -QCheckBox#checkboxPlayer1Connected::indicator:unchecked, -QCheckBox#checkboxPlayer2Connected::indicator:unchecked, -QCheckBox#checkboxPlayer3Connected::indicator:unchecked, -QCheckBox#checkboxPlayer4Connected::indicator:unchecked, -QCheckBox#checkboxPlayer5Connected::indicator:unchecked, -QCheckBox#checkboxPlayer6Connected::indicator:unchecked, -QCheckBox#checkboxPlayer7Connected::indicator:unchecked, -QCheckBox#checkboxPlayer8Connected::indicator:unchecked, -QGroupBox#groupConnectedController::indicator:unchecked { - border-radius: 2px; - border: 1px solid #929192; - background: transparent; - image: none; -} - QSpinBox#spinboxLStickRange, QSpinBox#spinboxRStickRange { padding: 4px 0px 5px 0px; @@ -1372,6 +1319,257 @@ QGroupBox#vibrationGroup::title { padding-right: 1px; } +QWidget#bottomPerGameInput, +QWidget#topControllerApplet, +QWidget#bottomControllerApplet, +QGroupBox#groupPlayer1Connected:checked, +QGroupBox#groupPlayer2Connected:checked, +QGroupBox#groupPlayer3Connected:checked, +QGroupBox#groupPlayer4Connected:checked, +QGroupBox#groupPlayer5Connected:checked, +QGroupBox#groupPlayer6Connected:checked, +QGroupBox#groupPlayer7Connected:checked, +QGroupBox#groupPlayer8Connected:checked { + background-color: #232629; +} + +QWidget#topPerGameInput, +QWidget#middleControllerApplet { + background-color: #31363b; +} + +QWidget#topPerGameInput QComboBox, +QWidget#middleControllerApplet QComboBox { + width: 119px; +} + +QRadioButton#radioDocked { + margin-left: -3px; +} + + +QRadioButton#radioUndocked { + margin-right: 5px; +} + +QWidget#connectedControllers { + background: transparent; +} + +QWidget#playersSupported, +QWidget#controllersSupported, +QWidget#controllerSupported1, +QWidget#controllerSupported2, +QWidget#controllerSupported3, +QWidget#controllerSupported4, +QWidget#controllerSupported5, +QWidget#controllerSupported6 { + border: none; + background: transparent; +} + +QGroupBox#groupPlayer1Connected, +QGroupBox#groupPlayer2Connected, +QGroupBox#groupPlayer3Connected, +QGroupBox#groupPlayer4Connected, +QGroupBox#groupPlayer5Connected, +QGroupBox#groupPlayer6Connected, +QGroupBox#groupPlayer7Connected, +QGroupBox#groupPlayer8Connected { + border: 1px solid #76797c; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; + margin-top: 0px; +} + +QGroupBox#groupPlayer1Connected:unchecked, +QGroupBox#groupPlayer2Connected:unchecked, +QGroupBox#groupPlayer3Connected:unchecked, +QGroupBox#groupPlayer4Connected:unchecked, +QGroupBox#groupPlayer5Connected:unchecked, +QGroupBox#groupPlayer6Connected:unchecked, +QGroupBox#groupPlayer7Connected:unchecked, +QGroupBox#groupPlayer8Connected:unchecked { + border: 1px solid #54575b; +} + +QGroupBox#groupPlayer1Connected::title, +QGroupBox#groupPlayer2Connected::title, +QGroupBox#groupPlayer3Connected::title, +QGroupBox#groupPlayer4Connected::title, +QGroupBox#groupPlayer5Connected::title, +QGroupBox#groupPlayer6Connected::title, +QGroupBox#groupPlayer7Connected::title, +QGroupBox#groupPlayer8Connected::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: -2px; + margin-right: -4px; + margin-bottom: 6px; +} + +QCheckBox#checkboxPlayer1Connected, +QCheckBox#checkboxPlayer2Connected, +QCheckBox#checkboxPlayer3Connected, +QCheckBox#checkboxPlayer4Connected, +QCheckBox#checkboxPlayer5Connected, +QCheckBox#checkboxPlayer6Connected, +QCheckBox#checkboxPlayer7Connected, +QCheckBox#checkboxPlayer8Connected { + spacing: 0px; +} + +QWidget#Player1LEDs, +QWidget#Player2LEDs, +QWidget#Player3LEDs, +QWidget#Player4LEDs, +QWidget#Player5LEDs, +QWidget#Player6LEDs, +QWidget#Player7LEDs, +QWidget#Player8LEDs { + background: transparent; +} + +QWidget#Player1LEDs QCheckBox, +QWidget#Player2LEDs QCheckBox, +QWidget#Player3LEDs QCheckBox, +QWidget#Player4LEDs QCheckBox, +QWidget#Player5LEDs QCheckBox, +QWidget#Player6LEDs QCheckBox, +QWidget#Player7LEDs QCheckBox, +QWidget#Player8LEDs QCheckBox { + spacing: 0px; + margin-bottom: 0px; + margin-right: 0px; +} + +QWidget#Player1LEDs QCheckBox::indicator, +QWidget#Player2LEDs QCheckBox::indicator, +QWidget#Player3LEDs QCheckBox::indicator, +QWidget#Player4LEDs QCheckBox::indicator, +QWidget#Player5LEDs QCheckBox::indicator, +QWidget#Player6LEDs QCheckBox::indicator, +QWidget#Player7LEDs QCheckBox::indicator, +QWidget#Player8LEDs QCheckBox::indicator { + width: 6px; + height: 6px; + margin-left: 0px; +} + +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { + width: 12px; + height: 12px; +} + +QCheckBox#checkboxPlayer1Connected::indicator, +QCheckBox#checkboxPlayer2Connected::indicator, +QCheckBox#checkboxPlayer3Connected::indicator, +QCheckBox#checkboxPlayer4Connected::indicator, +QCheckBox#checkboxPlayer5Connected::indicator, +QCheckBox#checkboxPlayer6Connected::indicator, +QCheckBox#checkboxPlayer7Connected::indicator, +QCheckBox#checkboxPlayer8Connected::indicator { + width: 14px; + height: 14px; +} + +QGroupBox#groupPlayer1Connected::indicator, +QGroupBox#groupPlayer2Connected::indicator, +QGroupBox#groupPlayer3Connected::indicator, +QGroupBox#groupPlayer4Connected::indicator, +QGroupBox#groupPlayer5Connected::indicator, +QGroupBox#groupPlayer6Connected::indicator, +QGroupBox#groupPlayer7Connected::indicator, +QGroupBox#groupPlayer8Connected::indicator { + width: 16px; + height: 16px; +} + +QWidget#Player1LEDs QCheckBox::indicator:checked, +QWidget#Player2LEDs QCheckBox::indicator:checked, +QWidget#Player3LEDs QCheckBox::indicator:checked, +QWidget#Player4LEDs QCheckBox::indicator:checked, +QWidget#Player5LEDs QCheckBox::indicator:checked, +QWidget#Player6LEDs QCheckBox::indicator:checked, +QWidget#Player7LEDs QCheckBox::indicator:checked, +QWidget#Player8LEDs QCheckBox::indicator:checked, +QGroupBox#groupPlayer1Connected::indicator:checked, +QGroupBox#groupPlayer2Connected::indicator:checked, +QGroupBox#groupPlayer3Connected::indicator:checked, +QGroupBox#groupPlayer4Connected::indicator:checked, +QGroupBox#groupPlayer5Connected::indicator:checked, +QGroupBox#groupPlayer6Connected::indicator:checked, +QGroupBox#groupPlayer7Connected::indicator:checked, +QGroupBox#groupPlayer8Connected::indicator:checked, +QCheckBox#checkboxPlayer1Connected::indicator:checked, +QCheckBox#checkboxPlayer2Connected::indicator:checked, +QCheckBox#checkboxPlayer3Connected::indicator:checked, +QCheckBox#checkboxPlayer4Connected::indicator:checked, +QCheckBox#checkboxPlayer5Connected::indicator:checked, +QCheckBox#checkboxPlayer6Connected::indicator:checked, +QCheckBox#checkboxPlayer7Connected::indicator:checked, +QCheckBox#checkboxPlayer8Connected::indicator:checked, +QGroupBox#groupConnectedController::indicator:checked { + border-radius: 2px; + border: 1px solid #929192; + background: #39ff14; + image: none; +} + +QWidget#Player1LEDs QCheckBox::indicator:unchecked, +QWidget#Player2LEDs QCheckBox::indicator:unchecked, +QWidget#Player3LEDs QCheckBox::indicator:unchecked, +QWidget#Player4LEDs QCheckBox::indicator:unchecked, +QWidget#Player5LEDs QCheckBox::indicator:unchecked, +QWidget#Player6LEDs QCheckBox::indicator:unchecked, +QWidget#Player7LEDs QCheckBox::indicator:unchecked, +QWidget#Player8LEDs QCheckBox::indicator:unchecked, +QGroupBox#groupPlayer1Connected::indicator:unchecked, +QGroupBox#groupPlayer2Connected::indicator:unchecked, +QGroupBox#groupPlayer3Connected::indicator:unchecked, +QGroupBox#groupPlayer4Connected::indicator:unchecked, +QGroupBox#groupPlayer5Connected::indicator:unchecked, +QGroupBox#groupPlayer6Connected::indicator:unchecked, +QGroupBox#groupPlayer7Connected::indicator:unchecked, +QGroupBox#groupPlayer8Connected::indicator:unchecked, +QCheckBox#checkboxPlayer1Connected::indicator:unchecked, +QCheckBox#checkboxPlayer2Connected::indicator:unchecked, +QCheckBox#checkboxPlayer3Connected::indicator:unchecked, +QCheckBox#checkboxPlayer4Connected::indicator:unchecked, +QCheckBox#checkboxPlayer5Connected::indicator:unchecked, +QCheckBox#checkboxPlayer6Connected::indicator:unchecked, +QCheckBox#checkboxPlayer7Connected::indicator:unchecked, +QCheckBox#checkboxPlayer8Connected::indicator:unchecked, +QGroupBox#groupConnectedController::indicator:unchecked { + border-radius: 2px; + border: 1px solid #929192; + background: transparent; + image: none; +} + +QWidget#controllerPlayer1, +QWidget#controllerPlayer2, +QWidget#controllerPlayer3, +QWidget#controllerPlayer4, +QWidget#controllerPlayer5, +QWidget#controllerPlayer6, +QWidget#controllerPlayer7, +QWidget#controllerPlayer8 { + background: transparent; +} + /* touchscreen mapping widget */ TouchScreenPreview { qproperty-dotHighlightColor: #3daee9; diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index a714e14759..9f6a0ff1df 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -2205,7 +2205,179 @@ QPushButton#buttonRefreshDevices { padding: 0px 0px; } +QSpinBox#spinboxLStickRange, +QSpinBox#spinboxRStickRange { + min-width: 38px; +} + +QGroupBox#motionGroup::indicator, +QGroupBox#vibrationGroup::indicator { + margin-left: 0px; +} + +QWidget#bottomPerGameInput QGroupBox#motionGroup, +QWidget#bottomPerGameInput QGroupBox#vibrationGroup, +QWidget#bottomPerGameInput QGroupBox#inputConfigGroup { + padding: 0px; +} + +QGroupBox#motionGroup::title, +QGroupBox#vibrationGroup::title { + spacing: 2px; + padding-left: 1px; + padding-right: 1px; +} + +QListWidget#selectorList { + background-color: #0f1922; +} + +QSpinBox, +QLineEdit, +QTreeView#hotkey_list, +QScrollArea#scrollArea QTreeView { + background-color: #0f1922; +} + +QWidget#bottomPerGameInput, +QWidget#topControllerApplet, +QWidget#bottomControllerApplet, +QGroupBox#groupPlayer1Connected:checked, +QGroupBox#groupPlayer2Connected:checked, +QGroupBox#groupPlayer3Connected:checked, +QGroupBox#groupPlayer4Connected:checked, +QGroupBox#groupPlayer5Connected:checked, +QGroupBox#groupPlayer6Connected:checked, +QGroupBox#groupPlayer7Connected:checked, +QGroupBox#groupPlayer8Connected:checked { + background-color: #0f1922; +} + +QWidget#topPerGameInput, +QWidget#middleControllerApplet { + background-color: #19232d; +} + +QWidget#topPerGameInput QComboBox, +QWidget#middleControllerApplet QComboBox { + padding-right: 2px; + width: 127px; +} + +QGroupBox#handheldGroup { + padding-left: 0px; +} + +QRadioButton#radioDocked { + margin-left: -1px; + padding-left: 0px; +} + +QRadioButton#radioDocked::indicator { + margin-left: 0px; +} + + +QRadioButton#radioUndocked { + margin-right: 2px; +} + +QWidget#connectedControllers { + background: transparent; +} + +QWidget#playersSupported, +QWidget#controllersSupported, +QWidget#controllerSupported1, +QWidget#controllerSupported2, +QWidget#controllerSupported3, +QWidget#controllerSupported4, +QWidget#controllerSupported5, +QWidget#controllerSupported6 { + border: none; + background: transparent; +} + +QGroupBox#groupPlayer1Connected, +QGroupBox#groupPlayer2Connected, +QGroupBox#groupPlayer3Connected, +QGroupBox#groupPlayer4Connected, +QGroupBox#groupPlayer5Connected, +QGroupBox#groupPlayer6Connected, +QGroupBox#groupPlayer7Connected, +QGroupBox#groupPlayer8Connected { + border: 1px solid #76797c; + border-radius: 3px; + padding: 0px; + min-height: 98px; + max-height: 98px; + margin-top: 0px; +} + + +QGroupBox#groupPlayer1Connected:unchecked, +QGroupBox#groupPlayer2Connected:unchecked, +QGroupBox#groupPlayer3Connected:unchecked, +QGroupBox#groupPlayer4Connected:unchecked, +QGroupBox#groupPlayer5Connected:unchecked, +QGroupBox#groupPlayer6Connected:unchecked, +QGroupBox#groupPlayer7Connected:unchecked, +QGroupBox#groupPlayer8Connected:unchecked { + border: 1px solid #32414b; +} + +QGroupBox#groupPlayer1Connected::title, +QGroupBox#groupPlayer2Connected::title, +QGroupBox#groupPlayer3Connected::title, +QGroupBox#groupPlayer4Connected::title, +QGroupBox#groupPlayer5Connected::title, +QGroupBox#groupPlayer6Connected::title, +QGroupBox#groupPlayer7Connected::title, +QGroupBox#groupPlayer8Connected::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding-left: 0px; + padding-right: 0px; + padding-top: 1px; + margin-left: -2px; + margin-right: -4px; + margin-bottom: 6px; +} +QCheckBox#checkboxPlayer1Connected, +QCheckBox#checkboxPlayer2Connected, +QCheckBox#checkboxPlayer3Connected, +QCheckBox#checkboxPlayer4Connected, +QCheckBox#checkboxPlayer5Connected, +QCheckBox#checkboxPlayer6Connected, +QCheckBox#checkboxPlayer7Connected, +QCheckBox#checkboxPlayer8Connected { + spacing: 0px; +} + +QWidget#connectedControllers QLabel { + padding: 0px; +} + +QWidget#Player1LEDs, +QWidget#Player2LEDs, +QWidget#Player3LEDs, +QWidget#Player4LEDs, +QWidget#Player5LEDs, +QWidget#Player6LEDs, +QWidget#Player7LEDs, +QWidget#Player8LEDs { + background: transparent; +} + +QWidget#Player1LEDs QCheckBox, +QWidget#Player2LEDs QCheckBox, +QWidget#Player3LEDs QCheckBox, +QWidget#Player4LEDs QCheckBox, +QWidget#Player5LEDs QCheckBox, +QWidget#Player6LEDs QCheckBox, +QWidget#Player7LEDs QCheckBox, +QWidget#Player8LEDs QCheckBox, QCheckBox#checkboxPlayer1Connected, QCheckBox#checkboxPlayer2Connected, QCheckBox#checkboxPlayer3Connected, @@ -2215,6 +2387,34 @@ QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { spacing: 0px; + padding-top: 0px; + padding-bottom: 0px; + background: transparent; +} + +QWidget#Player1LEDs QCheckBox::indicator, +QWidget#Player2LEDs QCheckBox::indicator, +QWidget#Player3LEDs QCheckBox::indicator, +QWidget#Player4LEDs QCheckBox::indicator, +QWidget#Player5LEDs QCheckBox::indicator, +QWidget#Player6LEDs QCheckBox::indicator, +QWidget#Player7LEDs QCheckBox::indicator, +QWidget#Player8LEDs QCheckBox::indicator { + width: 6px; + height: 6px; + margin-left: 0px; +} + +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, +QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { + width: 12px; + height: 12px; } QCheckBox#checkboxPlayer1Connected::indicator, @@ -2227,8 +2427,25 @@ QCheckBox#checkboxPlayer7Connected::indicator, QCheckBox#checkboxPlayer8Connected::indicator { width: 14px; height: 14px; + margin-left: 2px; } +QWidget#Player1LEDs QCheckBox::indicator:checked, +QWidget#Player2LEDs QCheckBox::indicator:checked, +QWidget#Player3LEDs QCheckBox::indicator:checked, +QWidget#Player4LEDs QCheckBox::indicator:checked, +QWidget#Player5LEDs QCheckBox::indicator:checked, +QWidget#Player6LEDs QCheckBox::indicator:checked, +QWidget#Player7LEDs QCheckBox::indicator:checked, +QWidget#Player8LEDs QCheckBox::indicator:checked, +QGroupBox#groupPlayer1Connected::indicator:checked, +QGroupBox#groupPlayer2Connected::indicator:checked, +QGroupBox#groupPlayer3Connected::indicator:checked, +QGroupBox#groupPlayer4Connected::indicator:checked, +QGroupBox#groupPlayer5Connected::indicator:checked, +QGroupBox#groupPlayer6Connected::indicator:checked, +QGroupBox#groupPlayer7Connected::indicator:checked, +QGroupBox#groupPlayer8Connected::indicator:checked, QCheckBox#checkboxPlayer1Connected::indicator:checked, QCheckBox#checkboxPlayer2Connected::indicator:checked, QCheckBox#checkboxPlayer3Connected::indicator:checked, @@ -2244,6 +2461,22 @@ QGroupBox#groupConnectedController::indicator:checked { image: none; } +QWidget#Player1LEDs QCheckBox::indicator:unchecked, +QWidget#Player2LEDs QCheckBox::indicator:unchecked, +QWidget#Player3LEDs QCheckBox::indicator:unchecked, +QWidget#Player4LEDs QCheckBox::indicator:unchecked, +QWidget#Player5LEDs QCheckBox::indicator:unchecked, +QWidget#Player6LEDs QCheckBox::indicator:unchecked, +QWidget#Player7LEDs QCheckBox::indicator:unchecked, +QWidget#Player8LEDs QCheckBox::indicator:unchecked, +QGroupBox#groupPlayer1Connected::indicator:unchecked, +QGroupBox#groupPlayer2Connected::indicator:unchecked, +QGroupBox#groupPlayer3Connected::indicator:unchecked, +QGroupBox#groupPlayer4Connected::indicator:unchecked, +QGroupBox#groupPlayer5Connected::indicator:unchecked, +QGroupBox#groupPlayer6Connected::indicator:unchecked, +QGroupBox#groupPlayer7Connected::indicator:unchecked, +QGroupBox#groupPlayer8Connected::indicator:unchecked, QCheckBox#checkboxPlayer1Connected::indicator:unchecked, QCheckBox#checkboxPlayer2Connected::indicator:unchecked, QCheckBox#checkboxPlayer3Connected::indicator:unchecked, @@ -2255,34 +2488,17 @@ QCheckBox#checkboxPlayer8Connected::indicator:unchecked, QGroupBox#groupConnectedController::indicator:unchecked { border-radius: 2px; border: 1px solid #929192; - background: transparent; + background: #19232d; image: none; } -QSpinBox#spinboxLStickRange, -QSpinBox#spinboxRStickRange { - min-width: 38px; +QWidget#controllerPlayer1, +QWidget#controllerPlayer2, +QWidget#controllerPlayer3, +QWidget#controllerPlayer4, +QWidget#controllerPlayer5, +QWidget#controllerPlayer6, +QWidget#controllerPlayer7, +QWidget#controllerPlayer8 { + background: transparent; } - -QGroupBox#motionGroup::indicator, -QGroupBox#vibrationGroup::indicator { - margin-left: 0px; -} - -QGroupBox#motionGroup::title, -QGroupBox#vibrationGroup::title { -spacing: 2px; - padding-left: 1px; - padding-right: 1px; -} - -QListWidget#selectorList { - background-color: #0f1922; -} - -QSpinBox, -QLineEdit, -QTreeView#hotkey_list, -QScrollArea#scrollArea QTreeView { - background-color: #0f1922; -} \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c85c9485f9..a39940e4c9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -126,6 +126,8 @@ add_library(core STATIC file_sys/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h + frontend/applets/controller.cpp + frontend/applets/controller.h frontend/applets/error.cpp frontend/applets/error.h frontend/applets/general_frontend.cpp @@ -244,6 +246,8 @@ add_library(core STATIC hle/service/am/applet_oe.h hle/service/am/applets/applets.cpp hle/service/am/applets/applets.h + hle/service/am/applets/controller.cpp + hle/service/am/applets/controller.h hle/service/am/applets/error.cpp hle/service/am/applets/error.h hle/service/am/applets/general_backend.cpp diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp new file mode 100644 index 0000000000..0fbc7932ce --- /dev/null +++ b/src/core/frontend/applets/controller.cpp @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +namespace Core::Frontend { + +ControllerApplet::~ControllerApplet() = default; + +DefaultControllerApplet::~DefaultControllerApplet() = default; + +void DefaultControllerApplet::ReconfigureControllers(std::function callback, + ControllerParameters parameters) const { + LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!"); + + auto& npad = + Core::System::GetInstance() + .ServiceManager() + .GetService("hid") + ->GetAppletResource() + ->GetController(Service::HID::HidController::NPad); + + auto& players = Settings::values.players; + + // Deduce the best configuration based on the input parameters. + for (std::size_t index = 0; index < players.size(); ++index) { + // First, disconnect all controllers regardless of the value of keep_controllers_connected. + // This makes it easy to connect the desired controllers. + npad.DisconnectNPadAtIndex(index); + } + + callback(); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h new file mode 100644 index 0000000000..0908f2b696 --- /dev/null +++ b/src/core/frontend/applets/controller.h @@ -0,0 +1,45 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Core::Frontend { + +using BorderColor = std::array; + +struct ControllerParameters { + s8 min_players{}; + s8 max_players{}; + bool keep_controllers_connected{}; + bool enable_single_mode{}; + bool enable_border_color{}; + std::vector border_colors{}; + bool allow_pro_controller{}; + bool allow_handheld{}; + bool allow_dual_joycons{}; + bool allow_left_joycon{}; + bool allow_right_joycon{}; +}; + +class ControllerApplet { +public: + virtual ~ControllerApplet(); + + virtual void ReconfigureControllers(std::function callback, + ControllerParameters parameters) const = 0; +}; + +class DefaultControllerApplet final : public ControllerApplet { +public: + ~DefaultControllerApplet() override; + + void ReconfigureControllers(std::function callback, + ControllerParameters parameters) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index c3261f3e60..4e0800f9aa 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -5,6 +5,7 @@ #include #include "common/assert.h" #include "core/core.h" +#include "core/frontend/applets/controller.h" #include "core/frontend/applets/error.h" #include "core/frontend/applets/general_frontend.h" #include "core/frontend/applets/profile_select.h" @@ -15,6 +16,7 @@ #include "core/hle/kernel/writable_event.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/controller.h" #include "core/hle/service/am/applets/error.h" #include "core/hle/service/am/applets/general_backend.h" #include "core/hle/service/am/applets/profile_select.h" @@ -140,14 +142,14 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, +AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, + ErrorApplet error, ParentalControlsApplet parental_controls, PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser, - ECommerceApplet e_commerce) - : parental_controls{std::move(parental_controls)}, error{std::move(error)}, - photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)}, - software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)}, - e_commerce{std::move(e_commerce)} {} + SoftwareKeyboard software_keyboard, WebBrowser web_browser) + : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)}, + parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)}, + profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)}, + web_browser{std::move(web_browser)} {} AppletFrontendSet::~AppletFrontendSet() = default; @@ -164,20 +166,37 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const { } void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { - if (set.parental_controls != nullptr) - frontend.parental_controls = std::move(set.parental_controls); - if (set.error != nullptr) - frontend.error = std::move(set.error); - if (set.photo_viewer != nullptr) - frontend.photo_viewer = std::move(set.photo_viewer); - if (set.profile_select != nullptr) - frontend.profile_select = std::move(set.profile_select); - if (set.software_keyboard != nullptr) - frontend.software_keyboard = std::move(set.software_keyboard); - if (set.web_browser != nullptr) - frontend.web_browser = std::move(set.web_browser); - if (set.e_commerce != nullptr) + if (set.controller != nullptr) { + frontend.controller = std::move(set.controller); + } + + if (set.e_commerce != nullptr) { frontend.e_commerce = std::move(set.e_commerce); + } + + if (set.error != nullptr) { + frontend.error = std::move(set.error); + } + + if (set.parental_controls != nullptr) { + frontend.parental_controls = std::move(set.parental_controls); + } + + if (set.photo_viewer != nullptr) { + frontend.photo_viewer = std::move(set.photo_viewer); + } + + if (set.profile_select != nullptr) { + frontend.profile_select = std::move(set.profile_select); + } + + if (set.software_keyboard != nullptr) { + frontend.software_keyboard = std::move(set.software_keyboard); + } + + if (set.web_browser != nullptr) { + frontend.web_browser = std::move(set.web_browser); + } } void AppletManager::SetDefaultAppletFrontendSet() { @@ -186,15 +205,23 @@ void AppletManager::SetDefaultAppletFrontendSet() { } void AppletManager::SetDefaultAppletsIfMissing() { - if (frontend.parental_controls == nullptr) { - frontend.parental_controls = - std::make_unique(); + if (frontend.controller == nullptr) { + frontend.controller = std::make_unique(); + } + + if (frontend.e_commerce == nullptr) { + frontend.e_commerce = std::make_unique(); } if (frontend.error == nullptr) { frontend.error = std::make_unique(); } + if (frontend.parental_controls == nullptr) { + frontend.parental_controls = + std::make_unique(); + } + if (frontend.photo_viewer == nullptr) { frontend.photo_viewer = std::make_unique(); } @@ -211,10 +238,6 @@ void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.web_browser == nullptr) { frontend.web_browser = std::make_unique(); } - - if (frontend.e_commerce == nullptr) { - frontend.e_commerce = std::make_unique(); - } } void AppletManager::ClearAll() { @@ -225,6 +248,8 @@ std::shared_ptr AppletManager::GetApplet(AppletId id) const { switch (id) { case AppletId::Auth: return std::make_shared(system, *frontend.parental_controls); + case AppletId::Controller: + return std::make_shared(system, *frontend.controller); case AppletId::Error: return std::make_shared(system, *frontend.error); case AppletId::ProfileSelect: diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index e75be86a23..a1f4cf8970 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -17,6 +17,7 @@ class System; } namespace Core::Frontend { +class ControllerApplet; class ECommerceApplet; class ErrorApplet; class ParentalControlsApplet; @@ -155,19 +156,20 @@ protected: }; struct AppletFrontendSet { - using ParentalControlsApplet = std::unique_ptr; + using ControllerApplet = std::unique_ptr; + using ECommerceApplet = std::unique_ptr; using ErrorApplet = std::unique_ptr; + using ParentalControlsApplet = std::unique_ptr; using PhotoViewer = std::unique_ptr; using ProfileSelect = std::unique_ptr; using SoftwareKeyboard = std::unique_ptr; using WebBrowser = std::unique_ptr; - using ECommerceApplet = std::unique_ptr; AppletFrontendSet(); - AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, - PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser, - ECommerceApplet e_commerce); + AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error, + ParentalControlsApplet parental_controls, PhotoViewer photo_viewer, + ProfileSelect profile_select, SoftwareKeyboard software_keyboard, + WebBrowser web_browser); ~AppletFrontendSet(); AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -176,13 +178,14 @@ struct AppletFrontendSet { AppletFrontendSet(AppletFrontendSet&&) noexcept; AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; - ParentalControlsApplet parental_controls; + ControllerApplet controller; + ECommerceApplet e_commerce; ErrorApplet error; + ParentalControlsApplet parental_controls; PhotoViewer photo_viewer; ProfileSelect profile_select; SoftwareKeyboard software_keyboard; WebBrowser web_browser; - ECommerceApplet e_commerce; }; class AppletManager { diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp new file mode 100644 index 0000000000..2a45a388ff --- /dev/null +++ b/src/core/hle/service/am/applets/controller.cpp @@ -0,0 +1,197 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/controller.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/controller.h" +#include "core/hle/service/hid/controllers/npad.h" + +namespace Service::AM::Applets { + +// This error code (0x183ACA) is thrown when the applet fails to initialize. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101}; +// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2. +[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102}; + +static Core::Frontend::ControllerParameters ConvertToFrontendParameters( + ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, + std::vector identification_colors) { + HID::Controller_NPad::NPadType npad_style_set; + npad_style_set.raw = private_arg.style_set; + + return { + .min_players = header.player_count_min, + .max_players = header.player_count_max, + .keep_controllers_connected = header.enable_take_over_connection, + .enable_single_mode = header.enable_single_mode, + .enable_border_color = header.enable_identification_color, + .border_colors = identification_colors, + .allow_pro_controller = npad_style_set.pro_controller == 1, + .allow_handheld = npad_style_set.handheld == 1, + .allow_dual_joycons = npad_style_set.joycon_dual == 1, + .allow_left_joycon = npad_style_set.joycon_left == 1, + .allow_right_joycon = npad_style_set.joycon_right == 1, + }; +} + +Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} + +Controller::~Controller() = default; + +void Controller::Initialize() { + Applet::Initialize(); + + LOG_INFO(Service_HID, "Initializing Controller Applet."); + + LOG_DEBUG(Service_HID, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + library_applet_version = LibraryAppletVersion{common_args.library_version}; + + const auto private_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(private_arg_storage != nullptr); + + const auto& private_arg = private_arg_storage->GetData(); + ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); + + std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate)); + ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), + "Unknown ControllerSupportArgPrivate revision={} with size={}", + library_applet_version, controller_private_arg.arg_private_size); + + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto user_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(user_arg_storage != nullptr); + + const auto& user_arg = user_arg_storage->GetData(); + switch (library_applet_version) { + case LibraryAppletVersion::Version3: + case LibraryAppletVersion::Version4: + case LibraryAppletVersion::Version5: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld)); + break; + case LibraryAppletVersion::Version7: + ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", + library_applet_version, controller_private_arg.arg_size); + ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + break; + } + break; + } + case ControllerSupportMode::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + default: { + UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); + break; + } + } +} + +bool Controller::TransactionComplete() const { + return complete; +} + +ResultCode Controller::GetStatus() const { + return status; +} + +void Controller::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void Controller::Execute() { + switch (controller_private_arg.mode) { + case ControllerSupportMode::ShowControllerSupport: { + const auto parameters = [this] { + switch (library_applet_version) { + case LibraryAppletVersion::Version3: + case LibraryAppletVersion::Version4: + case LibraryAppletVersion::Version5: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_old.header, + std::vector( + controller_user_arg_old.identification_colors.begin(), + controller_user_arg_old.identification_colors.end())); + case LibraryAppletVersion::Version7: + default: + return ConvertToFrontendParameters( + controller_private_arg, controller_user_arg_new.header, + std::vector( + controller_user_arg_new.identification_colors.begin(), + controller_user_arg_new.identification_colors.end())); + } + }(); + + is_single_mode = parameters.enable_single_mode; + + LOG_DEBUG( + Service_HID, + "Controller Parameters: min_players={}, max_players={}, keep_controllers_connected={}, " + "enable_single_mode={}, enable_border_color={}, allow_pro_controller={}, " + "allow_handheld={}, allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}", + parameters.min_players, parameters.max_players, parameters.keep_controllers_connected, + parameters.enable_single_mode, parameters.enable_border_color, + parameters.allow_pro_controller, parameters.allow_handheld, + parameters.allow_dual_joycons, parameters.allow_left_joycon, + parameters.allow_right_joycon); + + frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters); + break; + } + case ControllerSupportMode::ShowControllerStrapGuide: + case ControllerSupportMode::ShowControllerFirmwareUpdate: + default: { + ConfigurationComplete(); + break; + } + } +} + +void Controller::ConfigurationComplete() { + ControllerSupportResultInfo result_info{}; + + const auto& players = Settings::values.players; + + // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. + // Otherwise, only count connected players from P1-P8. + result_info.player_count = + is_single_mode ? 1 + : static_cast(std::count_if( + players.begin(), players.end() - 2, + [](Settings::PlayerInput player) { return player.connected; })); + + result_info.selected_id = HID::Controller_NPad::IndexToNPad( + std::distance(players.begin(), + std::find_if(players.begin(), players.end(), + [](Settings::PlayerInput player) { return player.connected; }))); + + result_info.result = 0; + + LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}", + result_info.player_count, result_info.selected_id, result_info.result); + + complete = true; + out_data = std::vector(sizeof(ControllerSupportResultInfo)); + std::memcpy(out_data.data(), &result_info, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared(std::move(out_data))); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h new file mode 100644 index 0000000000..90a78d5082 --- /dev/null +++ b/src/core/hle/service/am/applets/controller.h @@ -0,0 +1,119 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "core/hle/result.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Applets { + +using IdentificationColor = std::array; + +enum class LibraryAppletVersion : u32_le { + Version3 = 0x3, // 1.0.0 - 2.3.0 + Version4 = 0x4, // 3.0.0 - 5.1.0 + Version5 = 0x5, // 6.0.0 - 7.0.1 + Version7 = 0x7, // 8.0.0+ +}; + +enum class ControllerSupportMode : u8 { + ShowControllerSupport = 0, + ShowControllerStrapGuide = 1, + ShowControllerFirmwareUpdate = 2, +}; + +enum class ControllerSupportCaller : u8 { + Application = 0, + System = 1, +}; + +struct ControllerSupportArgPrivate { + u32 arg_private_size{}; + u32 arg_size{}; + bool flag_0{}; + bool flag_1{}; + ControllerSupportMode mode{}; + ControllerSupportCaller caller{}; + u32 style_set{}; + u32 joy_hold_type{}; +}; +static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, + "ControllerSupportArgPrivate has incorrect size."); + +struct ControllerSupportArgHeader { + s8 player_count_min{}; + s8 player_count_max{}; + bool enable_take_over_connection{}; + bool enable_left_justify{}; + bool enable_permit_joy_dual{}; + bool enable_single_mode{}; + bool enable_identification_color{}; +}; +static_assert(sizeof(ControllerSupportArgHeader) == 0x7, + "ControllerSupportArgHeader has incorrect size."); + +// LibraryAppletVersion 0x3, 0x4, 0x5 +struct ControllerSupportArgOld { + ControllerSupportArgHeader header{}; + std::array identification_colors{}; + bool enable_explain_text{}; + std::array, 4> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgOld) == 0x21C, + "ControllerSupportArgOld has incorrect size."); + +// LibraryAppletVersion 0x7 +struct ControllerSupportArgNew { + ControllerSupportArgHeader header{}; + std::array identification_colors{}; + bool enable_explain_text{}; + std::array, 8> explain_text{}; +}; +static_assert(sizeof(ControllerSupportArgNew) == 0x430, + "ControllerSupportArgNew has incorrect size."); + +struct ControllerSupportResultInfo { + s8 player_count{}; + INSERT_PADDING_BYTES(3); + u32 selected_id{}; + u32 result{}; +}; +static_assert(sizeof(ControllerSupportResultInfo) == 0xC, + "ControllerSupportResultInfo has incorrect size."); + +class Controller final : public Applet { +public: + explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_); + ~Controller() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void ConfigurationComplete(); + +private: + const Core::Frontend::ControllerApplet& frontend; + + LibraryAppletVersion library_applet_version; + ControllerSupportArgPrivate controller_private_arg; + ControllerSupportArgOld controller_user_arg_old; + ControllerSupportArgNew controller_user_arg_new; + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; + bool is_single_mode{false}; + std::vector out_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 45fde8df25..efb953d93c 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -193,7 +193,8 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { controller.battery_level[0] = BATTERY_FULL; controller.battery_level[1] = BATTERY_FULL; controller.battery_level[2] = BATTERY_FULL; - styleset_changed_events[controller_idx].writable->Signal(); + + SignalStyleSetChangedEvent(IndexToNPad(controller_idx)); } void Controller_NPad::OnInit() { @@ -518,13 +519,17 @@ void Controller_NPad::VibrateController(const std::vector& controller_ids, last_processed_vibration = vibrations.back(); } +Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { + return last_processed_vibration; +} + std::shared_ptr Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; return styleset_event.readable; } -Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { - return last_processed_vibration; +void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const { + styleset_changed_events[NPadIdToIndex(npad_id)].writable->Signal(); } void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { @@ -534,7 +539,7 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected) { if (!connected) { - DisconnectNPad(IndexToNPad(npad_index)); + DisconnectNPadAtIndex(npad_index); return; } @@ -554,16 +559,19 @@ void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::siz } void Controller_NPad::DisconnectNPad(u32 npad_id) { - const auto npad_index = NPadIdToIndex(npad_id); - connected_controllers[npad_index].is_connected = false; + DisconnectNPadAtIndex(NPadIdToIndex(npad_id)); +} + +void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) { Settings::values.players[npad_index].connected = false; + connected_controllers[npad_index].is_connected = false; auto& controller = shared_memory_entries[npad_index]; controller.joy_styles.raw = 0; // Zero out controller.device_type.raw = 0; controller.properties.raw = 0; - styleset_changed_events[npad_index].writable->Signal(); + SignalStyleSetChangedEvent(IndexToNPad(npad_index)); } void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { @@ -650,13 +658,13 @@ void Controller_NPad::ClearAllConnectedControllers() { } void Controller_NPad::DisconnectAllConnectedControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { controller.is_connected = false; } } void Controller_NPad::ConnectAllDisconnectedControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { if (controller.type != NPadControllerType::None && !controller.is_connected) { controller.is_connected = true; } @@ -664,7 +672,7 @@ void Controller_NPad::ConnectAllDisconnectedControllers() { } void Controller_NPad::ClearAllControllers() { - for (ControllerHolder& controller : connected_controllers) { + for (auto& controller : connected_controllers) { controller.type = NPadControllerType::None; controller.is_connected = false; } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 75ce5b7313..40c7633768 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -115,15 +115,19 @@ public: void VibrateController(const std::vector& controller_ids, const std::vector& vibrations); - std::shared_ptr GetStyleSetChangedEvent(u32 npad_id) const; Vibration GetLastVibration() const; + std::shared_ptr GetStyleSetChangedEvent(u32 npad_id) const; + void SignalStyleSetChangedEvent(u32 npad_id) const; + // Adds a new controller at an index. void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index); // Adds a new controller at an index with connection status. void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected); void DisconnectNPad(u32 npad_id); + void DisconnectNPadAtIndex(std::size_t index); + void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; LedPattern GetLedPattern(u32 npad_id); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3ea4e56017..cc0291b15b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -9,6 +9,9 @@ add_executable(yuzu about_dialog.cpp about_dialog.h aboutdialog.ui + applets/controller.cpp + applets/controller.h + applets/controller.ui applets/error.cpp applets/error.h applets/profile_select.cpp @@ -62,12 +65,15 @@ add_executable(yuzu configuration/configure_input.cpp configuration/configure_input.h configuration/configure_input.ui - configuration/configure_input_player.cpp - configuration/configure_input_player.h - configuration/configure_input_player.ui configuration/configure_input_advanced.cpp configuration/configure_input_advanced.h configuration/configure_input_advanced.ui + configuration/configure_input_dialog.cpp + configuration/configure_input_dialog.h + configuration/configure_input_dialog.ui + configuration/configure_input_player.cpp + configuration/configure_input_player.h + configuration/configure_input_player.ui configuration/configure_motion_touch.cpp configuration/configure_motion_touch.h configuration/configure_motion_touch.ui diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp new file mode 100644 index 0000000000..8ccf61be0a --- /dev/null +++ b/src/yuzu/applets/controller.cpp @@ -0,0 +1,568 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "ui_controller.h" +#include "yuzu/applets/controller.h" +#include "yuzu/configuration/configure_input_dialog.h" +#include "yuzu/main.h" + +constexpr std::array, 8> led_patterns = {{ + {1, 0, 0, 0}, + {1, 1, 0, 0}, + {1, 1, 1, 0}, + {1, 1, 1, 1}, + {1, 0, 0, 1}, + {1, 0, 1, 0}, + {1, 0, 1, 1}, + {0, 1, 1, 0}, +}}; + +namespace { + +void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, + bool connected) { + Core::System& system{Core::System::GetInstance()}; + + if (!system.IsPoweredOn()) { + return; + } + + Service::SM::ServiceManager& sm = system.ServiceManager(); + + auto& npad = + sm.GetService("hid") + ->GetAppletResource() + ->GetController(Service::HID::HidController::NPad); + + npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); +} + +bool IsControllerCompatible(Settings::ControllerType controller_type, + Core::Frontend::ControllerParameters parameters) { + switch (controller_type) { + case Settings::ControllerType::ProController: + return parameters.allow_pro_controller; + case Settings::ControllerType::DualJoyconDetached: + return parameters.allow_dual_joycons; + case Settings::ControllerType::LeftJoycon: + return parameters.allow_left_joycon; + case Settings::ControllerType::RightJoycon: + return parameters.allow_right_joycon; + case Settings::ControllerType::Handheld: + return parameters.enable_single_mode && parameters.allow_handheld; + default: + return false; + } +} + +/// Maps the controller type combobox index to Controller Type enum +constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { + switch (index) { + case 0: + default: + return Settings::ControllerType::ProController; + case 1: + return Settings::ControllerType::DualJoyconDetached; + case 2: + return Settings::ControllerType::LeftJoycon; + case 3: + return Settings::ControllerType::RightJoycon; + case 4: + return Settings::ControllerType::Handheld; + } +} + +/// Maps the Controller Type enum to controller type combobox index +constexpr int GetIndexFromControllerType(Settings::ControllerType type) { + switch (type) { + case Settings::ControllerType::ProController: + default: + return 0; + case Settings::ControllerType::DualJoyconDetached: + return 1; + case Settings::ControllerType::LeftJoycon: + return 2; + case Settings::ControllerType::RightJoycon: + return 3; + case Settings::ControllerType::Handheld: + return 4; + } +} + +} // namespace + +QtControllerSelectorDialog::QtControllerSelectorDialog( + QWidget* parent, Core::Frontend::ControllerParameters parameters_, InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), ui(std::make_unique()), + parameters(std::move(parameters_)), input_subsystem(input_subsystem_) { + ui->setupUi(this); + + player_widgets = { + ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4, + ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8, + }; + + player_groupboxes = { + ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected, + ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected, + ui->groupPlayer7Connected, ui->groupPlayer8Connected, + }; + + connected_controller_icons = { + ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4, + ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8, + }; + + led_patterns_boxes = {{ + {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3, + ui->checkboxPlayer1LED4}, + {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3, + ui->checkboxPlayer2LED4}, + {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3, + ui->checkboxPlayer3LED4}, + {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3, + ui->checkboxPlayer4LED4}, + {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3, + ui->checkboxPlayer5LED4}, + {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3, + ui->checkboxPlayer6LED4}, + {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3, + ui->checkboxPlayer7LED4}, + {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3, + ui->checkboxPlayer8LED4}, + }}; + + emulated_controllers = { + ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated, + ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated, + ui->comboPlayer7Emulated, ui->comboPlayer8Emulated, + }; + + player_labels = { + ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4, + ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8, + }; + + connected_controller_labels = { + ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3, + ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6, + ui->labelConnectedPlayer7, ui->labelConnectedPlayer8, + }; + + connected_controller_checkboxes = { + ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, + ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, + ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, + }; + + for (std::size_t i = 0; i < player_widgets.size(); ++i) { + connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { + if (checked) { + for (std::size_t index = 0; index <= i; ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } else { + for (std::size_t index = i; index < player_widgets.size(); ++index) { + connected_controller_checkboxes[index]->setChecked(checked); + } + } + }); + + connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), + [this, i](int) { + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + CheckIfParametersMet(); + }); + + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + player_groupboxes[i]->setChecked(state == Qt::Checked); + UpdateControllerIcon(i); + UpdateControllerState(i); + UpdateLEDPattern(i); + UpdateBorderColor(i); + CheckIfParametersMet(); + }); + + if (i == 0) { + connect(emulated_controllers[i], qOverload(&QComboBox::currentIndexChanged), + [this](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index) == + Settings::ControllerType::Handheld); + }); + } + } + + connect(ui->inputConfigButton, &QPushButton::clicked, this, + &QtControllerSelectorDialog::CallConfigureInputDialog); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &QtControllerSelectorDialog::ApplyConfiguration); + + SetSupportedControllers(); + DisableUnsupportedPlayers(); + LoadConfiguration(); + + // If keep_controllers_connected is false, forcefully disconnect all controllers + if (!parameters.keep_controllers_connected) { + for (auto player : player_groupboxes) { + player->setChecked(false); + } + } + + CheckIfParametersMet(); + + resize(0, 0); +} + +QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; + +void QtControllerSelectorDialog::ApplyConfiguration() { + const bool pre_docked_mode = Settings::values.use_docked_mode; + Settings::values.use_docked_mode = ui->radioDocked->isChecked(); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); + + Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); +} + +void QtControllerSelectorDialog::CallConfigureInputDialog() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + ConfigureInputDialog dialog(this, max_supported_players, input_subsystem); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + dialog.ApplyConfiguration(); + + LoadConfiguration(); + CheckIfParametersMet(); +} + +void QtControllerSelectorDialog::CheckIfParametersMet() { + // Here, we check and validate the current configuration against all applicable parameters. + const auto& players = Settings::values.players; + + const auto num_connected_players = + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [this](const QGroupBox* player) { return player->isChecked(); }); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + // First, check against the number of connected players. + if (num_connected_players < min_supported_players || + num_connected_players > max_supported_players) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return; + } + + // Next, check against all connected controllers. + const auto all_controllers_compatible = [this] { + for (std::size_t index = 0; index < player_widgets.size(); ++index) { + // Skip controllers that are not used, we only care about the currently connected ones. + if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) { + continue; + } + + const auto compatible = IsControllerCompatible( + GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()), + parameters); + + // If any controller is found to be incompatible, return false early. + if (!compatible) { + return false; + } + } + + // Reaching here means all currently connected controllers are compatible. + return true; + }(); + + if (!all_controllers_compatible) { + parameters_met = false; + ui->buttonBox->setEnabled(parameters_met); + return; + } + + parameters_met = true; + ui->buttonBox->setEnabled(parameters_met); +} + +void QtControllerSelectorDialog::SetSupportedControllers() { + const QString theme = [this] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + if (parameters.enable_single_mode && parameters.allow_handheld) { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme)); + } else { + ui->controllerSupported1->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme)); + } + + if (parameters.allow_dual_joycons) { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme)); + } else { + ui->controllerSupported2->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme)); + } + + if (parameters.allow_left_joycon) { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme)); + } else { + ui->controllerSupported3->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme)); + } + + if (parameters.allow_right_joycon) { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme)); + } else { + ui->controllerSupported4->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); + } + + if (parameters.allow_pro_controller) { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); + } else { + ui->controllerSupported5->setStyleSheet( + QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ") + .arg(theme)); + } + + // enable_single_mode overrides min_players and max_players. + if (parameters.enable_single_mode) { + ui->numberSupportedLabel->setText(QStringLiteral("1")); + return; + } + + if (parameters.min_players == parameters.max_players) { + ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players)); + } else { + ui->numberSupportedLabel->setText( + QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players)); + } +} + +void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString stylesheet = [this, player_index] { + switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) { + case Settings::ControllerType::ProController: + return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_left%0); "); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/applet_joycon_right%0); "); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/applet_handheld%0); "); + default: + return QString{}; + } + }(); + + const QString theme = [this] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme)); + player_labels[player_index]->hide(); +} + +void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) { + auto& player = Settings::values.players[player_index]; + + player.controller_type = + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()); + player.connected = player_groupboxes[player_index]->isChecked(); + + // Player 2-8 + if (player_index != 0) { + UpdateController(player.controller_type, player_index, player.connected); + return; + } + + // Player 1 and Handheld + auto& handheld = Settings::values.players[8]; + // If Handheld is selected, copy all the settings from Player 1 to Handheld. + if (player.controller_type == Settings::ControllerType::Handheld) { + handheld = player; + handheld.connected = player_groupboxes[player_index]->isChecked(); + player.connected = false; // Disconnect Player 1 + } else { + player.connected = player_groupboxes[player_index]->isChecked(); + handheld.connected = false; // Disconnect Handheld + } + + UpdateController(player.controller_type, player_index, player.connected); + UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected); +} + +void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { + if (!player_groupboxes[player_index]->isChecked() || + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) == + Settings::ControllerType::Handheld) { + led_patterns_boxes[player_index][0]->setChecked(false); + led_patterns_boxes[player_index][1]->setChecked(false); + led_patterns_boxes[player_index][2]->setChecked(false); + led_patterns_boxes[player_index][3]->setChecked(false); + return; + } + + led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]); + led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]); + led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]); + led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]); +} + +void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) { + if (!parameters.enable_border_color || player_index >= parameters.max_players || + player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) { + return; + } + + player_groupboxes[player_index]->setStyleSheet( + player_groupboxes[player_index]->styleSheet().append( + QStringLiteral("QGroupBox#groupPlayer%1Connected:checked " + "{ border: 1px solid rgba(%2, %3, %4, %5); }") + .arg(player_index + 1) + .arg(parameters.border_colors[player_index][0]) + .arg(parameters.border_colors[player_index][1]) + .arg(parameters.border_colors[player_index][2]) + .arg(parameters.border_colors[player_index][3]))); +} + +void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { + // Disallow changing the console mode if the controller type is handheld. + ui->radioDocked->setEnabled(!is_handheld); + ui->radioUndocked->setEnabled(!is_handheld); + + ui->radioDocked->setChecked(Settings::values.use_docked_mode); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); + + // Also force into undocked mode if the controller type is handheld. + if (is_handheld) { + ui->radioUndocked->setChecked(true); + } +} + +void QtControllerSelectorDialog::DisableUnsupportedPlayers() { + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + switch (max_supported_players) { + case 0: + default: + UNREACHABLE(); + return; + case 1: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + ui->widgetSpacer4->hide(); + break; + case 2: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + ui->widgetSpacer3->hide(); + break; + case 3: + ui->widgetSpacer->hide(); + ui->widgetSpacer2->hide(); + break; + case 4: + ui->widgetSpacer->hide(); + break; + case 5: + case 6: + case 7: + case 8: + break; + } + + for (std::size_t index = max_supported_players; index < player_widgets.size(); ++index) { + // Disconnect any unsupported players here and disable or hide them if applicable. + Settings::values.players[index].connected = false; + UpdateController(Settings::values.players[index].controller_type, index, false); + // Hide the player widgets when max_supported_controllers is less than or equal to 4. + if (max_supported_players <= 4) { + player_widgets[index]->hide(); + } + + // Disable and hide the following to prevent these from interaction. + player_widgets[index]->setDisabled(true); + connected_controller_checkboxes[index]->setDisabled(true); + connected_controller_labels[index]->hide(); + connected_controller_checkboxes[index]->hide(); + } +} + +void QtControllerSelectorDialog::LoadConfiguration() { + for (std::size_t index = 0; index < player_widgets.size(); ++index) { + const auto connected = Settings::values.players[index].connected || + (index == 0 && Settings::values.players[8].connected); + player_groupboxes[index]->setChecked(connected); + emulated_controllers[index]->setCurrentIndex( + GetIndexFromControllerType(Settings::values.players[index].controller_type)); + } + + UpdateDockedState(Settings::values.players[8].connected); + + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); +} + +QtControllerSelector::QtControllerSelector(GMainWindow& parent) { + connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, + &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, + &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); +} + +QtControllerSelector::~QtControllerSelector() = default; + +void QtControllerSelector::ReconfigureControllers( + std::function callback, Core::Frontend::ControllerParameters parameters) const { + this->callback = std::move(callback); + emit MainWindowReconfigureControllers(parameters); +} + +void QtControllerSelector::MainWindowReconfigureFinished() { + // Acquire the HLE mutex + std::lock_guard lock(HLE::g_hle_lock); + callback(); +} diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h new file mode 100644 index 0000000000..1ec290e6c5 --- /dev/null +++ b/src/yuzu/applets/controller.h @@ -0,0 +1,125 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "core/frontend/applets/controller.h" + +class GMainWindow; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class QtControllerSelectorDialog; +} + +class QtControllerSelectorDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtControllerSelectorDialog(QWidget* parent, + Core::Frontend::ControllerParameters parameters_, + InputCommon::InputSubsystem* input_subsystem_); + ~QtControllerSelectorDialog() override; + +private: + // Applies the current configuration. + void ApplyConfiguration(); + + // Initializes the "Configure Input" Dialog. + void CallConfigureInputDialog(); + + // Checks the current configuration against the given parameters and + // sets the value of parameters_met. + void CheckIfParametersMet(); + + // Sets the controller icons for "Supported Controller Types". + void SetSupportedControllers(); + + // Updates the controller icons per player. + void UpdateControllerIcon(std::size_t player_index); + + // Updates the controller state (type and connection status) per player. + void UpdateControllerState(std::size_t player_index); + + // Updates the LED pattern per player. + void UpdateLEDPattern(std::size_t player_index); + + // Updates the border color per player. + void UpdateBorderColor(std::size_t player_index); + + // Updates the console mode. + void UpdateDockedState(bool is_handheld); + + // Disables and disconnects unsupported players based on the given parameters. + void DisableUnsupportedPlayers(); + + // Loads the current input configuration into the frontend applet. + void LoadConfiguration(); + + std::unique_ptr ui; + + // Parameters sent in from the backend HLE applet. + Core::Frontend::ControllerParameters parameters; + + InputCommon::InputSubsystem* input_subsystem; + + // This is true if and only if all parameters are met. Otherwise, this is false. + // This determines whether the "Ok" button can be clicked to exit the applet. + bool parameters_met{false}; + + // Widgets encapsulating the groupboxes and comboboxes per player. + std::array player_widgets; + + // Groupboxes encapsulating the controller icons and LED patterns per player. + std::array player_groupboxes; + + // Icons for currently connected controllers/players. + std::array connected_controller_icons; + + // Labels that represent the player numbers in place of the controller icons. + std::array player_labels; + + // LED patterns for currently connected controllers/players. + std::array, 8> led_patterns_boxes; + + // Comboboxes with a list of emulated controllers per player. + std::array emulated_controllers; + + // Labels representing the number of connected controllers + // above the "Connected Controllers" checkboxes. + std::array connected_controller_labels; + + // Checkboxes representing the "Connected Controllers". + std::array connected_controller_checkboxes; +}; + +class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { + Q_OBJECT + +public: + explicit QtControllerSelector(GMainWindow& parent); + ~QtControllerSelector() override; + + void ReconfigureControllers(std::function callback, + Core::Frontend::ControllerParameters parameters) const override; + +signals: + void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const; + +private: + void MainWindowReconfigureFinished(); + + mutable std::function callback; +}; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui new file mode 100644 index 0000000000..d7db46613b --- /dev/null +++ b/src/yuzu/applets/controller.ui @@ -0,0 +1,2432 @@ + + + QtControllerSelectorDialog + + + + 0 + 0 + 839 + 630 + + + + Controller Applet + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + + + 0 + + + 10 + + + 0 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 75 + true + + + + Supported Controller Types: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + + + + 0 + 0 + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + + + + 70 + 70 + + + + + 70 + 70 + + + + + 0 + + + 0 + + + 16 + + + 14 + + + 16 + + + + + + 75 + true + + + + Players: + + + Qt::AlignCenter + + + false + + + + + + + + 14 + + + + 1 - 8 + + + Qt::AlignCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P4 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P2 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P1 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + Handheld + + + + + + + + + Use Current Config + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P3 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + false + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P7 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P8 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P5 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::LeftToRight + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + true + + + false + + + + 7 + + + 14 + + + 7 + + + 14 + + + 4 + + + + + + + + + 16 + + + + + P6 + + + + + + + + + + false + + + + 0 + 10 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + 0 + 10 + + + + + + + + + Pro Controller + + + + + Dual Joycons + + + + + Left Joycon + + + + + Right Joycon + + + + + + + + + Use Current Config + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 25 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 25 + 20 + + + + + + + + + + + + 0 + 25 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + + + + + + + + + 15 + + + 15 + + + 8 + + + 15 + + + 15 + + + + + + 16777215 + 16777215 + + + + Console Mode + + + + 6 + + + 6 + + + 6 + + + 3 + + + 6 + + + + + Docked + + + true + + + + + + + Undocked + + + + + + + + + + Vibration + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 65 + 0 + + + + + 65 + 16777215 + + + + % + + + 1 + + + 200 + + + 100 + + + + + + + + + + Motion + + + true + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 57 + 0 + + + + + 55 + 16777215 + + + + min-width: 55px; + + + Configure + + + + + + + + + + Input Config + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 65 + 16777215 + + + + min-width: 55px; + + + Open + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 3 + + + + + + + + + + + + Controllers + + + + + + + + + + + + + + 1 + + + Qt::AlignCenter + + + + + + + + + + + + + + Qt::LeftToRight + + + false + + + + + + + 2 + + + Qt::AlignCenter + + + + + + + 4 + + + Qt::AlignCenter + + + + + + + 3 + + + Qt::AlignCenter + + + + + + + Connected + + + + + + + + + + + + + + 5 + + + Qt::AlignCenter + + + + + + + + + + + + + + 7 + + + Qt::AlignCenter + + + + + + + + + + + + + + 6 + + + Qt::AlignCenter + + + + + + + 8 + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + false + + + QDialogButtonBox::Ok + + + + + + + + + + + + + + + buttonBox + accepted() + QtControllerSelectorDialog + accept() + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index ae3e31762c..3befcc7396 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -70,7 +70,7 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { +void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, std::size_t max_players) { player_controllers = { new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), @@ -93,6 +93,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; + std::array player_connected_labels = { + ui->label, ui->label_3, ui->label_4, ui->label_5, + ui->label_6, ui->label_7, ui->label_8, ui->label_9, + }; + for (std::size_t i = 0; i < player_tabs.size(); ++i) { player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); @@ -112,6 +117,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { player_controllers[i]->ConnectPlayer(state == Qt::Checked); }); + + // Remove/hide all the elements that exceed max_players, if applicable. + if (i >= max_players) { + ui->tabWidget->removeTab(static_cast(max_players)); + player_connected[i]->hide(); + player_connected_labels[i]->hide(); + } } // Only the first player can choose handheld mode so connect the signal just to player 1 connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged, @@ -175,8 +187,7 @@ void ConfigureInput::RetranslateUI() { void ConfigureInput::LoadConfiguration() { LoadPlayerControllerIndices(); - UpdateDockedState(Settings::values.players[0].controller_type == - Settings::ControllerType::Handheld); + UpdateDockedState(Settings::values.players[8].connected); ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); } @@ -208,14 +219,14 @@ void ConfigureInput::RestoreDefaults() { } void ConfigureInput::UpdateDockedState(bool is_handheld) { - // If the controller type is handheld only, disallow changing docked mode + // Disallow changing the console mode if the controller type is handheld. ui->radioDocked->setEnabled(!is_handheld); ui->radioUndocked->setEnabled(!is_handheld); ui->radioDocked->setChecked(Settings::values.use_docked_mode); ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); - // If its handheld only, force docked mode off (since you can't play handheld in a dock) + // Also force into undocked mode if the controller type is handheld. if (is_handheld) { ui->radioUndocked->setChecked(true); } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index d08a24f96a..0e8b2fd4ee 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -37,7 +37,7 @@ public: ~ConfigureInput() override; /// Initializes the input dialog with the given input subsystem. - void Initialize(InputCommon::InputSubsystem* input_subsystem_); + void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8); /// Save all button configurations to settings file. void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp new file mode 100644 index 0000000000..1866003c28 --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.cpp @@ -0,0 +1,37 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_input_dialog.h" +#include "yuzu/configuration/configure_input_dialog.h" + +ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players, + InputCommon::InputSubsystem* input_subsystem) + : QDialog(parent), ui(std::make_unique()), + input_widget(new ConfigureInput(this)) { + ui->setupUi(this); + + input_widget->Initialize(input_subsystem, max_players); + + ui->inputLayout->addWidget(input_widget); + + RetranslateUI(); +} + +ConfigureInputDialog::~ConfigureInputDialog() = default; + +void ConfigureInputDialog::ApplyConfiguration() { + input_widget->ApplyConfiguration(); +} + +void ConfigureInputDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureInputDialog::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h new file mode 100644 index 0000000000..d1bd865f9e --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.h @@ -0,0 +1,38 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "yuzu/configuration/configure_input.h" + +class QPushButton; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureInputDialog; +} + +class ConfigureInputDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players, + InputCommon::InputSubsystem* input_subsystem); + ~ConfigureInputDialog() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr ui; + + ConfigureInput* input_widget; +}; diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_dialog.ui new file mode 100644 index 0000000000..b92ddb2001 --- /dev/null +++ b/src/yuzu/configuration/configure_input_dialog.ui @@ -0,0 +1,57 @@ + + + ConfigureInputDialog + + + + 0 + 0 + 70 + 540 + + + + Configure Input + + + + 2 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + + + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ConfigureInputDialog + accept() + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a1b61d119c..2ac8344a36 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -11,6 +11,7 @@ #endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/controller.h" #include "applets/error.h" #include "applets/profile_select.h" #include "applets/software_keyboard.h" @@ -19,7 +20,9 @@ #include "configuration/configure_per_game.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" +#include "core/frontend/applets/controller.h" #include "core/frontend/applets/general_frontend.h" +#include "core/frontend/applets/software_keyboard.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -84,7 +87,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" -#include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" @@ -283,6 +285,19 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) { + QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get()); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + emit ControllerSelectorReconfigureFinished(); + + UpdateStatusButtons(); +} + void GMainWindow::ProfileSelectorSelectProfile() { const Service::Account::ProfileManager manager; int index = 0; @@ -291,10 +306,12 @@ void GMainWindow::ProfileSelectorSelectProfile() { dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); + if (dialog.exec() == QDialog::Rejected) { emit ProfileSelectorFinishedSelection(std::nullopt); return; } + index = dialog.GetIndex(); } @@ -966,13 +983,14 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetFilesystem(vfs); system.SetAppletFrontendSet({ - nullptr, // Parental Controls - std::make_unique(*this), // - nullptr, // Photo Viewer - std::make_unique(*this), // - std::make_unique(*this), // - std::make_unique(*this), // - nullptr, // E-Commerce + std::make_unique(*this), // Controller Selector + nullptr, // E-Commerce + std::make_unique(*this), // Error Display + nullptr, // Parental Controls + nullptr, // Photo Viewer + std::make_unique(*this), // Profile Selector + std::make_unique(*this), // Software Keyboard + std::make_unique(*this), // Web Browser }); system.RegisterHostThread(); @@ -2047,6 +2065,7 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); + qRegisterMetaType("Core::Frontend::ControllerParameters"); qRegisterMetaType( "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType("Core::System::ResultStatus"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0ce66a1caf..afcfa68a9b 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -37,6 +37,7 @@ enum class InstalledEntryType; class GameListPlaceholder; namespace Core::Frontend { +struct ControllerParameters; struct SoftwareKeyboardParameters; } // namespace Core::Frontend @@ -116,9 +117,12 @@ signals: void UpdateInstallProgress(); + void ControllerSelectorReconfigureFinished(); + void ErrorDisplayFinished(); void ProfileSelectorFinishedSelection(std::optional uuid); + void SoftwareKeyboardFinishedText(std::optional text); void SoftwareKeyboardFinishedCheckDialog(); @@ -127,6 +131,8 @@ signals: public slots: void OnLoadComplete(); + void ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters); void ErrorDisplayDisplayError(QString body); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);