diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 7aa4f3e348..5f3f37f75d 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -11,6 +11,8 @@ target_sources(pcsx2-qt PRIVATE AutoUpdaterDialog.cpp AutoUpdaterDialog.h AutoUpdaterDialog.ui + ColorPickerButton.cpp + ColorPickerButton.h CoverDownloadDialog.cpp CoverDownloadDialog.h CoverDownloadDialog.ui @@ -50,6 +52,7 @@ target_sources(pcsx2-qt PRIVATE Settings/ControllerBindingWidget_DualShock2.ui Settings/ControllerBindingWidgets.cpp Settings/ControllerBindingWidgets.h + Settings/ControllerLEDSettingsDialog.ui Settings/ControllerGlobalSettingsWidget.cpp Settings/ControllerGlobalSettingsWidget.h Settings/ControllerGlobalSettingsWidget.ui diff --git a/pcsx2-qt/ColorPickerButton.cpp b/pcsx2-qt/ColorPickerButton.cpp new file mode 100644 index 0000000000..470e878970 --- /dev/null +++ b/pcsx2-qt/ColorPickerButton.cpp @@ -0,0 +1,66 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" +#include "ColorPickerButton.h" +#include "QtUtils.h" + +#include + +ColorPickerButton::ColorPickerButton(QWidget* parent) + : QPushButton(parent) +{ + connect(this, &QPushButton::clicked, this, &ColorPickerButton::onClicked); + updateBackgroundColor(); +} + +u32 ColorPickerButton::color() +{ + return m_color; +} + +void ColorPickerButton::setColor(u32 rgb) +{ + if (m_color == rgb) + return; + + m_color = rgb; + updateBackgroundColor(); +} + +void ColorPickerButton::updateBackgroundColor() +{ + setStyleSheet(QStringLiteral("background-color: #%1;").arg(static_cast(m_color), 8, 16, QChar('0'))); +} + +void ColorPickerButton::onClicked() +{ + const u32 red = (m_color >> 16) & 0xff; + const u32 green = (m_color >> 8) & 0xff; + const u32 blue = m_color & 0xff; + + const QColor initial(QColor::fromRgb(red, green, blue)); + const QColor selected(QColorDialog::getColor(initial, QtUtils::GetRootWidget(this), tr("Select LED Color"))); + + // QColorDialog returns Invalid on cancel, and apparently initial == Invalid is true... + if (!selected.isValid() || initial == selected) + return; + + const u32 new_rgb = + (static_cast(selected.red()) << 16) | (static_cast(selected.green()) << 8) | static_cast(selected.blue()); + m_color = new_rgb; + updateBackgroundColor(); + emit colorChanged(new_rgb); +} diff --git a/pcsx2-qt/ColorPickerButton.h b/pcsx2-qt/ColorPickerButton.h new file mode 100644 index 0000000000..53427d5521 --- /dev/null +++ b/pcsx2-qt/ColorPickerButton.h @@ -0,0 +1,42 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include "common/Pcsx2Defs.h" +#include + +class ColorPickerButton : public QPushButton +{ + Q_OBJECT + +public: + ColorPickerButton(QWidget* parent); + +Q_SIGNALS: + void colorChanged(quint32 new_color); + +public Q_SLOTS: + quint32 color(); + void setColor(quint32 rgb); + +private Q_SLOTS: + void onClicked(); + +private: + void updateBackgroundColor(); + + u32 m_color = 0; +}; diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp index f122c2a91f..53f079a917 100644 --- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp @@ -22,6 +22,10 @@ #include "QtUtils.h" #include "SettingWidgetBinder.h" +#ifdef SDL_BUILD +#include "pcsx2/Frontend/SDLInputSource.h" +#endif + ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog) : QWidget(parent) , m_dialog(dialog) @@ -30,8 +34,16 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, SettingsInterface* sif = dialog->getProfileSettingsInterface(); +#ifdef SDL_BUILD SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false); + connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled); + connect(m_ui.ledSettings, &QToolButton::clicked, this, &ControllerGlobalSettingsWidget::ledSettingsClicked); +#else + m_ui.enableSDLSource->setEnabled(false); + m_ui.ledSettings->setEnabled(false); +#endif + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false); ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort1, "Pad", "MultitapPort1", false); ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort2, "Pad", "MultitapPort2", false); @@ -66,12 +78,13 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, m_ui.profileSettings = nullptr; } - connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled); for (QCheckBox* cb : {m_ui.multitapPort1, m_ui.multitapPort2}) connect(cb, &QCheckBox::stateChanged, this, [this]() { emit bindingSetupChanged(); }); - connect(m_ui.pointerXScale, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); }); - connect(m_ui.pointerYScale, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); }); + connect(m_ui.pointerXScale, &QSlider::valueChanged, this, + [this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); }); + connect(m_ui.pointerYScale, &QSlider::valueChanged, this, + [this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); }); m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerXScale->value())); m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerYScale->value())); @@ -106,4 +119,40 @@ void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled() { const bool enabled = m_ui.enableSDLSource->isChecked(); m_ui.enableSDLEnhancedMode->setEnabled(enabled); + m_ui.ledSettings->setEnabled(enabled); +} + +void ControllerGlobalSettingsWidget::ledSettingsClicked() +{ + ControllerLEDSettingsDialog dialog(this, m_dialog); + dialog.exec(); +} + +ControllerLEDSettingsDialog::ControllerLEDSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog) + : QDialog(parent) + , m_dialog(dialog) +{ + m_ui.setupUi(this); + + linkButton(m_ui.SDL0LED, 0); + linkButton(m_ui.SDL1LED, 1); + linkButton(m_ui.SDL2LED, 2); + linkButton(m_ui.SDL3LED, 3); + + connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept); +} + +ControllerLEDSettingsDialog::~ControllerLEDSettingsDialog() = default; + +void ControllerLEDSettingsDialog::linkButton(ColorPickerButton* button, u32 player_id) +{ +#ifdef SDL_BUILD + std::string key(fmt::format("Player{}LED", player_id)); + const u32 current_value = SDLInputSource::ParseRGBForPlayerId(m_dialog->getStringValue("SDLExtra", key.c_str(), ""), player_id); + button->setColor(current_value); + + connect(button, &ColorPickerButton::colorChanged, this, [this, player_id, key = std::move(key)](u32 new_rgb) { + m_dialog->setStringValue("SDLExtra", key.c_str(), fmt::format("{:06X}", new_rgb).c_str()); + }); +#endif } diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h index 476971585a..e748c523cd 100644 --- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h @@ -20,7 +20,10 @@ #include #include +#include "ColorPickerButton.h" + #include "ui_ControllerGlobalSettingsWidget.h" +#include "ui_ControllerLEDSettingsDialog.h" class ControllerSettingsDialog; @@ -38,9 +41,26 @@ public: Q_SIGNALS: void bindingSetupChanged(); -private: +private Q_SLOTS: void updateSDLOptionsEnabled(); + void ledSettingsClicked(); +private: Ui::ControllerGlobalSettingsWidget m_ui; ControllerSettingsDialog* m_dialog; }; + +class ControllerLEDSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + ControllerLEDSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog); + ~ControllerLEDSettingsDialog(); + +private: + void linkButton(ColorPickerButton* button, u32 player_id); + + Ui::ControllerLEDSettingsDialog m_ui; + ControllerSettingsDialog* m_dialog; +}; diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui index 434863a563..e01f940672 100644 --- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui @@ -50,11 +50,27 @@ - - - DualShock 4 / DualSense Enhanced Mode - - + + + + + DualShock 4 / DualSense Enhanced Mode + + + + + + + Controller LED Settings + + + + .. + + + + + diff --git a/pcsx2-qt/Settings/ControllerLEDSettingsDialog.ui b/pcsx2-qt/Settings/ControllerLEDSettingsDialog.ui new file mode 100644 index 0000000000..4fd71b4331 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerLEDSettingsDialog.ui @@ -0,0 +1,83 @@ + + + ControllerLEDSettingsDialog + + + + 0 + 0 + 501 + 108 + + + + Controller LED Settings + + + + + + SDL-0 LED + + + + + + + + + + + + SDL-1 LED + + + + + + + + + + + + SDL-2 LED + + + + + + + + + + + + SDL-3 LED + + + + + + + + + + + + QDialogButtonBox::Close + + + + + + + + ColorPickerButton + QPushButton +
ColorPickerButton.h
+
+
+ + +
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index dee3c01bc6..51a4441383 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -46,6 +46,7 @@ $(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\demangler\include;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories); + $(SolutionDir)3rdparty\sdl2\include;$(SolutionDir)3rdparty\sdl2\SDL\include;%(AdditionalIncludeDirectories) $(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories) %(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Models @@ -135,6 +136,7 @@ + @@ -211,6 +213,7 @@ + @@ -292,6 +295,7 @@ + @@ -414,6 +418,9 @@ Document + + Document + @@ -430,4 +437,4 @@ - \ No newline at end of file + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 5d392fc6a3..15d9889b58 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -27,9 +27,7 @@ - - Resources - + @@ -313,6 +311,10 @@ moc + + + moc + @@ -459,6 +461,7 @@ Debugger\Models + @@ -577,6 +580,9 @@ Debugger + + Settings + diff --git a/pcsx2-qt/resources/icons/black/svg/lightbulb-line.svg b/pcsx2-qt/resources/icons/black/svg/lightbulb-line.svg new file mode 100644 index 0000000000..9370abc1a6 --- /dev/null +++ b/pcsx2-qt/resources/icons/black/svg/lightbulb-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/white/svg/lightbulb-line.svg b/pcsx2-qt/resources/icons/white/svg/lightbulb-line.svg new file mode 100644 index 0000000000..454dc0fe79 --- /dev/null +++ b/pcsx2-qt/resources/icons/white/svg/lightbulb-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc index 38947f69f5..676395d341 100644 --- a/pcsx2-qt/resources/resources.qrc +++ b/pcsx2-qt/resources/resources.qrc @@ -37,6 +37,7 @@ icons/black/svg/image-fill.svg icons/black/svg/keyboard-line.svg icons/black/svg/layout-grid-line.svg + icons/black/svg/lightbulb-line.svg icons/black/svg/list-check.svg icons/black/svg/login-box-line.svg icons/black/svg/pause-line.svg @@ -96,6 +97,7 @@ icons/white/svg/image-fill.svg icons/white/svg/keyboard-line.svg icons/white/svg/layout-grid-line.svg + icons/white/svg/lightbulb-line.svg icons/white/svg/list-check.svg icons/white/svg/login-box-line.svg icons/white/svg/pause-line.svg diff --git a/pcsx2/Frontend/SDLInputSource.cpp b/pcsx2/Frontend/SDLInputSource.cpp index 0f687df728..b1889ae852 100644 --- a/pcsx2/Frontend/SDLInputSource.cpp +++ b/pcsx2/Frontend/SDLInputSource.cpp @@ -98,6 +98,18 @@ static constexpr const char* s_sdl_hat_direction_names[] = { // clang-format on }; +static constexpr const char* s_sdl_default_led_colors[] = { + "0000ff", // SDL-0 + "ff0000", // SDL-1 + "00ff00", // SDL-2 + "ffff00", // SDL-3 +}; + +static void SetControllerRGBLED(SDL_GameController* gc, u32 color) +{ + SDL_GameControllerSetLED(gc, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); +} + SDLInputSource::SDLInputSource() = default; SDLInputSource::~SDLInputSource() @@ -159,6 +171,39 @@ void SDLInputSource::LoadSettings(SettingsInterface& si) { m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false); m_sdl_hints = si.GetKeyValueList("SDLHints"); + + for (u32 i = 0; i < MAX_LED_COLORS; i++) + { + const u32 color = GetRGBForPlayerId(si, i); + if (m_led_colors[i] == color) + continue; + + m_led_colors[i] = color; + + const auto it = GetControllerDataForPlayerId(i); + if (it == m_controllers.end() || !it->game_controller || !SDL_GameControllerHasLED(it->game_controller)) + continue; + + SetControllerRGBLED(it->game_controller, color); + } +} + +u32 SDLInputSource::GetRGBForPlayerId(SettingsInterface& si, u32 player_id) +{ + return ParseRGBForPlayerId( + si.GetStringValue("SDLExtra", fmt::format("Player{}LED", player_id).c_str(), s_sdl_default_led_colors[player_id]), + player_id); +} + +u32 SDLInputSource::ParseRGBForPlayerId(const std::string_view& str, u32 player_id) +{ + if (player_id >= MAX_LED_COLORS) + return 0; + + const u32 default_color = StringUtil::FromChars(s_sdl_default_led_colors[player_id], 16).value_or(0); + const u32 color = StringUtil::FromChars(str, 16).value_or(default_color); + + return color; } void SDLInputSource::SetHints() @@ -617,6 +662,11 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) if (!cd.haptic && !cd.use_game_controller_rumble) Console.Warning("(SDLInputSource) Rumble is not supported on '%s'", name); + if (player_id >= 0 && static_cast(player_id) < MAX_LED_COLORS && gcontroller && SDL_GameControllerHasLED(gcontroller)) + { + SetControllerRGBLED(gcontroller, m_led_colors[player_id]); + } + m_controllers.push_back(std::move(cd)); InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name); @@ -668,7 +718,7 @@ bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, ev->button)); const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ? s_sdl_generic_binding_button_mapping[ev->button] : - GenericInputBinding::Unknown; + GenericInputBinding::Unknown; InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key); return true; } diff --git a/pcsx2/Frontend/SDLInputSource.h b/pcsx2/Frontend/SDLInputSource.h index 02ef309292..e04ca70759 100644 --- a/pcsx2/Frontend/SDLInputSource.h +++ b/pcsx2/Frontend/SDLInputSource.h @@ -26,6 +26,8 @@ class SettingsInterface; class SDLInputSource final : public InputSource { public: + static constexpr u32 MAX_LED_COLORS = 4; + SDLInputSource(); ~SDLInputSource(); @@ -48,6 +50,9 @@ public: SDL_Joystick* GetJoystickForDevice(const std::string_view& device); + static u32 GetRGBForPlayerId(SettingsInterface& si, u32 player_id); + static u32 ParseRGBForPlayerId(const std::string_view& str, u32 player_id); + private: struct ControllerData { @@ -92,5 +97,6 @@ private: bool m_sdl_subsystem_initialized = false; bool m_controller_enhanced_mode = false; + std::array m_led_colors{}; std::vector> m_sdl_hints; };