diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 367f1dd93..90c18ee36 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -27,6 +27,8 @@ set(SRCS cheatmanagerdialog.ui collapsiblewidget.cpp collapsiblewidget.h + colorpickerbutton.cpp + colorpickerbutton.h consolesettingswidget.cpp consolesettingswidget.h consolesettingswidget.ui @@ -42,6 +44,7 @@ set(SRCS controllerglobalsettingswidget.cpp controllerglobalsettingswidget.h controllerglobalsettingswidget.ui + controllerledsettingsdialog.ui controllermacroeditwidget.ui controllermacrowidget.ui controllersettingsdialog.cpp diff --git a/src/duckstation-qt/colorpickerbutton.cpp b/src/duckstation-qt/colorpickerbutton.cpp new file mode 100644 index 000000000..52ed76fee --- /dev/null +++ b/src/duckstation-qt/colorpickerbutton.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#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"))); + if (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/src/duckstation-qt/colorpickerbutton.h b/src/duckstation-qt/colorpickerbutton.h new file mode 100644 index 000000000..c15908301 --- /dev/null +++ b/src/duckstation-qt/colorpickerbutton.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once +#include "common/types.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/src/duckstation-qt/controllerglobalsettingswidget.cpp b/src/duckstation-qt/controllerglobalsettingswidget.cpp index 17d3788f5..beeed27e5 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.cpp +++ b/src/duckstation-qt/controllerglobalsettingswidget.cpp @@ -7,6 +7,8 @@ #include "qtutils.h" #include "settingwidgetbinder.h" +#include "frontend-common/sdl_input_source.h" + ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog) : QWidget(parent), m_dialog(dialog) { @@ -14,10 +16,18 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, SettingsInterface* sif = dialog->getProfileSettingsInterface(); +#ifdef WITH_SDL2 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", 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 + #ifdef _WIN32 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDInputSource, "InputSources", "DInput", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false); @@ -27,6 +37,7 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, m_ui.enableXInputSource->setEnabled(false); m_ui.enableRawInput->setEnabled(false); #endif + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false); SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.multitapMode, "ControllerPorts", "MultitapMode", &Settings::ParseMultitapModeName, &Settings::GetMultitapModeName, Settings::DEFAULT_MULTITAP_MODE); @@ -52,8 +63,6 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, m_ui.profileSettings = nullptr; } - connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, - &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled); connect(m_ui.multitapMode, &QComboBox::currentIndexChanged, this, [this]() { emit bindingSetupChanged(); }); connect(m_ui.pointerXScale, &QSlider::valueChanged, this, @@ -90,8 +99,44 @@ void ControllerGlobalSettingsWidget::removeDeviceFromList(const QString& identif } } +void ControllerGlobalSettingsWidget::ledSettingsClicked() +{ + ControllerLEDSettingsDialog dialog(this, m_dialog); + dialog.exec(); +} + void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled() { const bool enabled = m_ui.enableSDLSource->isChecked(); m_ui.enableSDLEnhancedMode->setEnabled(enabled); + m_ui.ledSettings->setEnabled(enabled); +} + +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 WITH_SDL2 + 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/src/duckstation-qt/controllerglobalsettingswidget.h b/src/duckstation-qt/controllerglobalsettingswidget.h index d6c0ce1d0..cb34fed51 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.h +++ b/src/duckstation-qt/controllerglobalsettingswidget.h @@ -3,12 +3,17 @@ #pragma once #include "common/types.h" + #include +#include #include #include #include +#include "colorpickerbutton.h" + #include "ui_controllerglobalsettingswidget.h" +#include "ui_controllerledsettingsdialog.h" class ControllerSettingsDialog; @@ -28,7 +33,23 @@ Q_SIGNALS: private: void updateSDLOptionsEnabled(); + void ledSettingsClicked(); 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/src/duckstation-qt/controllerglobalsettingswidget.ui b/src/duckstation-qt/controllerglobalsettingswidget.ui index fd3ed2b38..bcb411796 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.ui +++ b/src/duckstation-qt/controllerglobalsettingswidget.ui @@ -151,11 +151,27 @@ - - - DualShock 4 / DualSense Enhanced Mode - - + + + + + DualShock 4 / DualSense Enhanced Mode + + + + + + + Controller LED Settings + + + + .. + + + + + diff --git a/src/duckstation-qt/controllerledsettingsdialog.ui b/src/duckstation-qt/controllerledsettingsdialog.ui new file mode 100644 index 000000000..87f9aa9c2 --- /dev/null +++ b/src/duckstation-qt/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/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 465ab924e..f01010de3 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -12,6 +12,7 @@ + @@ -79,6 +80,7 @@ + @@ -232,6 +234,7 @@ + @@ -319,6 +322,9 @@ + + Document + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index a484f2ef8..e98ec568c 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -91,6 +91,9 @@ + + + @@ -150,6 +153,8 @@ + + @@ -189,6 +194,8 @@ + + diff --git a/src/duckstation-qt/resources/icons/black/svg/lightbulb-line.svg b/src/duckstation-qt/resources/icons/black/svg/lightbulb-line.svg new file mode 100644 index 000000000..9370abc1a --- /dev/null +++ b/src/duckstation-qt/resources/icons/black/svg/lightbulb-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/duckstation-qt/resources/icons/white/svg/lightbulb-line.svg b/src/duckstation-qt/resources/icons/white/svg/lightbulb-line.svg new file mode 100644 index 000000000..454dc0fe7 --- /dev/null +++ b/src/duckstation-qt/resources/icons/white/svg/lightbulb-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/duckstation-qt/resources/resources.qrc b/src/duckstation-qt/resources/resources.qrc index 7fad1efe8..5c833fe9b 100644 --- a/src/duckstation-qt/resources/resources.qrc +++ b/src/duckstation-qt/resources/resources.qrc @@ -181,6 +181,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/mouse-line.svg icons/black/svg/paint-brush-line.svg @@ -512,6 +513,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/mouse-line.svg icons/white/svg/paint-brush-line.svg diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index 2dd5d869e..6787bf87c 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -101,8 +101,8 @@ if(SDL2_FOUND) sdl_input_source.h ) target_compile_definitions(frontend-common PUBLIC "WITH_SDL2=1") - target_include_directories(frontend-common PRIVATE ${SDL2_INCLUDE_DIRS}) - target_link_libraries(frontend-common PRIVATE ${SDL2_LIBRARIES}) + target_include_directories(frontend-common PUBLIC ${SDL2_INCLUDE_DIRS}) + target_link_libraries(frontend-common PUBLIC ${SDL2_LIBRARIES}) # Copy bundled SDL2 to output on Windows. if(WIN32) diff --git a/src/frontend-common/sdl_input_source.cpp b/src/frontend-common/sdl_input_source.cpp index 4ce09d84d..d8d7eeb0f 100644 --- a/src/frontend-common/sdl_input_source.cpp +++ b/src/frontend-common/sdl_input_source.cpp @@ -88,6 +88,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() @@ -149,6 +161,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() @@ -624,6 +669,12 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) if (!cd.haptic && !cd.use_game_controller_rumble) Log_VerbosePrintf("(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); diff --git a/src/frontend-common/sdl_input_source.h b/src/frontend-common/sdl_input_source.h index 5daf1d341..5cd1db13f 100644 --- a/src/frontend-common/sdl_input_source.h +++ b/src/frontend-common/sdl_input_source.h @@ -14,6 +14,8 @@ class SettingsInterface; class SDLInputSource final : public InputSource { public: + static constexpr u32 MAX_LED_COLORS = 4; + SDLInputSource(); ~SDLInputSource(); @@ -38,6 +40,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 { @@ -82,5 +87,6 @@ private: bool m_sdl_subsystem_initialized = false; bool m_controller_enhanced_mode = false; + std::array m_led_colors{}; std::vector> m_sdl_hints; };