diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index c34db23c1e..cd933da17a 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -292,9 +292,6 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index), ir_sin(0), ir_cos(1 m_extension->attachments.emplace_back(new WiimoteEmu::Drums(m_reg_ext)); m_extension->attachments.emplace_back(new WiimoteEmu::Turntable(m_reg_ext)); - m_extension->boolean_settings.emplace_back( - m_motion_plus_setting = new ControllerEmu::BooleanSetting(_trans("Motion Plus"), false)); - // rumble groups.emplace_back(m_rumble = new ControllerEmu::ControlGroup(_trans("Rumble"))); m_rumble->controls.emplace_back(m_motor = new ControllerEmu::Output(_trans("Motor"))); diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 6dc9dd409e..363e5d9b0c 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -65,6 +65,7 @@ set(SRCS Config/Mapping/MappingBool.cpp Config/Mapping/MappingButton.cpp Config/Mapping/MappingCommon.cpp + Config/Mapping/MappingIndicator.cpp Config/Mapping/MappingNumeric.cpp Config/Mapping/MappingWidget.cpp Config/Mapping/MappingWindow.cpp diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt2/Config/Mapping/MappingButton.cpp index ca1aa5157a..147602e7a8 100644 --- a/Source/Core/DolphinQt2/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt2/Config/Mapping/MappingButton.cpp @@ -7,6 +7,9 @@ #include #include #include +#include + +#include #include "DolphinQt2/Config/Mapping/MappingButton.h" @@ -16,6 +19,7 @@ #include "DolphinQt2/Config/Mapping/MappingWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/QtUtils/BlockUserInputFilter.h" +#include "DolphinQt2/Settings.h" #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" @@ -26,11 +30,41 @@ static QString EscapeAmpersand(QString&& string) return string.replace(QStringLiteral("&"), QStringLiteral("&&")); } -MappingButton::MappingButton(MappingWidget* widget, ControlReference* ref) +MappingButton::MappingButton(MappingWidget* widget, ControlReference* ref, bool indicator) : ElidedButton(EscapeAmpersand(QString::fromStdString(ref->GetExpression()))), m_parent(widget), m_reference(ref) { Connect(); + setToolTip( + tr("Left-click to detect input.\nMiddle-click to clear.\nRight-click for more options.")); + if (!m_reference->IsInput() || !indicator) + return; + + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, [this] { + if (!isActiveWindow()) + return; + + Settings::Instance().SetControllerStateNeeded(true); + + auto state = m_reference->State(); + + QFont f = m_parent->font(); + QPalette p = m_parent->palette(); + + if (state != 0) + { + f.setBold(true); + p.setColor(QPalette::ButtonText, Qt::red); + } + + setFont(f); + setPalette(p); + + Settings::Instance().SetControllerStateNeeded(false); + }); + + m_timer->start(1000 / 30); } void MappingButton::Connect() @@ -85,6 +119,7 @@ void MappingButton::Clear() { m_reference->SetExpression(""); m_parent->SaveSettings(); + Update(); } void MappingButton::Update() @@ -105,7 +140,7 @@ void MappingButton::mouseReleaseEvent(QMouseEvent* event) else emit AdvancedPressed(); return; - case Qt::MouseButton::MiddleButton: + case Qt::MouseButton::MidButton: Clear(); return; case Qt::MouseButton::RightButton: diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingButton.h b/Source/Core/DolphinQt2/Config/Mapping/MappingButton.h index a8894119e7..10ee152381 100644 --- a/Source/Core/DolphinQt2/Config/Mapping/MappingButton.h +++ b/Source/Core/DolphinQt2/Config/Mapping/MappingButton.h @@ -11,12 +11,13 @@ class ControlReference; class MappingWidget; class QEvent; class QMouseEvent; +class QTimer; class MappingButton : public ElidedButton { Q_OBJECT public: - MappingButton(MappingWidget* widget, ControlReference* ref); + MappingButton(MappingWidget* widget, ControlReference* ref, bool indicator); void Clear(); void Update(); @@ -33,4 +34,5 @@ private: MappingWidget* m_parent; ControlReference* m_reference; + QTimer* m_timer; }; diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt2/Config/Mapping/MappingIndicator.cpp new file mode 100644 index 0000000000..c10a6d9f5c --- /dev/null +++ b/Source/Core/DolphinQt2/Config/Mapping/MappingIndicator.cpp @@ -0,0 +1,287 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Config/Mapping/MappingIndicator.h" + +#include +#include + +#include +#include + +#include + +#include "InputCommon/ControlReference/ControlReference.h" +#include "InputCommon/ControllerEmu/Control/Control.h" +#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" +#include "InputCommon/ControllerEmu/Setting/NumericSetting.h" +#include "InputCommon/ControllerInterface/Device.h" + +#include "DolphinQt2/Settings.h" + +MappingIndicator::MappingIndicator(ControllerEmu::ControlGroup* group) : m_group(group) +{ + setMinimumHeight(128); + + switch (m_group->type) + { + case ControllerEmu::GroupType::Cursor: + BindCursorControls(false); + break; + case ControllerEmu::GroupType::Stick: + BindStickControls(); + break; + case ControllerEmu::GroupType::Tilt: + BindCursorControls(true); + break; + case ControllerEmu::GroupType::MixedTriggers: + BindMixedTriggersControls(); + break; + default: + break; + } + + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, [this] { repaint(); }); + m_timer->start(1000 / 30); +} + +void MappingIndicator::BindCursorControls(bool tilt) +{ + m_cursor_up = m_group->controls[0]->control_ref.get(); + m_cursor_down = m_group->controls[1]->control_ref.get(); + m_cursor_left = m_group->controls[2]->control_ref.get(); + m_cursor_right = m_group->controls[3]->control_ref.get(); + + if (!tilt) + { + m_cursor_forward = m_group->controls[4]->control_ref.get(); + m_cursor_backward = m_group->controls[5]->control_ref.get(); + + m_cursor_center = m_group->numeric_settings[0].get(); + m_cursor_width = m_group->numeric_settings[1].get(); + m_cursor_height = m_group->numeric_settings[2].get(); + m_cursor_deadzone = m_group->numeric_settings[3].get(); + } + else + { + m_cursor_deadzone = m_group->numeric_settings[0].get(); + } +} + +void MappingIndicator::BindStickControls() +{ + m_stick_up = m_group->controls[0]->control_ref.get(); + m_stick_down = m_group->controls[1]->control_ref.get(); + m_stick_left = m_group->controls[2]->control_ref.get(); + m_stick_right = m_group->controls[3]->control_ref.get(); + m_stick_modifier = m_group->controls[4]->control_ref.get(); + + m_stick_radius = m_group->numeric_settings[0].get(); + m_stick_deadzone = m_group->numeric_settings[1].get(); +} + +void MappingIndicator::BindMixedTriggersControls() +{ + m_mixed_triggers_l_button = m_group->controls[0]->control_ref.get(); + m_mixed_triggers_r_button = m_group->controls[1]->control_ref.get(); + m_mixed_triggers_l_analog = m_group->controls[2]->control_ref.get(); + m_mixed_triggers_r_analog = m_group->controls[3]->control_ref.get(); + + m_mixed_triggers_threshold = m_group->numeric_settings[0].get(); +} + +static ControlState PollControlState(ControlReference* ref) +{ + Settings::Instance().SetControllerStateNeeded(true); + + auto state = ref->State(); + + Settings::Instance().SetControllerStateNeeded(false); + + if (state != 0) + return state; + else + return 0; +} + +void MappingIndicator::DrawCursor(bool tilt) +{ + float centerx = width() / 2., centery = height() / 2.; + + QPainter p(this); + + float width = 64, height = 64; + float deadzone = m_cursor_deadzone->GetValue() * 48; + + if (!tilt) + { + float depth = centery - PollControlState(m_cursor_forward) * this->height() / 2.5 + + PollControlState(m_cursor_backward) * this->height() / 2.5; + + p.fillRect(0, depth, this->width(), 4, Qt::gray); + + width *= m_cursor_width->GetValue(); + height *= m_cursor_height->GetValue(); + } + + float curx = centerx - 4 - std::min(PollControlState(m_cursor_left), 0.5) * width + + std::min(PollControlState(m_cursor_right), 0.5) * width, + cury = centery - 4 - std::min(PollControlState(m_cursor_up), 0.5) * height + + std::min(PollControlState(m_cursor_down), 0.5) * height; + + // Draw background + p.setBrush(Qt::white); + p.setPen(Qt::black); + p.drawRect(centerx - (width / 2), centery - (height / 2), width, height); + + // Draw deadzone + p.setBrush(Qt::lightGray); + p.drawEllipse(centerx - (deadzone / 2), centery - (deadzone / 2), deadzone, deadzone); + + // Draw cursor + p.fillRect(curx, cury, 8, 8, Qt::red); +} + +void MappingIndicator::DrawStick() +{ + float centerx = width() / 2., centery = height() / 2.; + + bool c_stick = m_group->name == "C-Stick"; + bool classic_controller = m_group->name == "Left Stick" || m_group->name == "Right Stick"; + + float ratio = 1; + + if (c_stick) + ratio = 1.; + else if (classic_controller) + ratio = 0.9f; + + // Polled values + float mod = PollControlState(m_stick_modifier) ? 0.5 : 1; + float radius = m_stick_radius->GetValue(); + float curx = -PollControlState(m_stick_left) + PollControlState(m_stick_right), + cury = -PollControlState(m_stick_up) + PollControlState(m_stick_down); + // The maximum deadzone value covers 50% of the stick area + float deadzone = m_stick_deadzone->GetValue() / 2.; + + // Size parameters + float max_size = (height() / 2.5) / ratio; + float stick_size = (height() / 3.) / ratio; + + // Emulated cursor position + float virt_curx, virt_cury; + + if (abs(curx) < deadzone && abs(cury) < deadzone) + { + virt_curx = virt_cury = 0; + } + else + { + virt_curx = curx * mod; + virt_cury = cury * mod; + } + + // Coordinates for an octagon + std::array radius_octagon = { + QPointF(centerx, centery + stick_size), // Bottom + QPointF(centerx + stick_size / sqrt(2), centery + stick_size / sqrt(2)), // Bottom Right + QPointF(centerx + stick_size, centery), // Right + QPointF(centerx + stick_size / sqrt(2), centery - stick_size / sqrt(2)), // Top Right + QPointF(centerx, centery - stick_size), // Top + QPointF(centerx - stick_size / sqrt(2), centery - stick_size / sqrt(2)), // Top Left + QPointF(centerx - stick_size, centery), // Left + QPointF(centerx - stick_size / sqrt(2), centery + stick_size / sqrt(2)) // Bottom Left + }; + + QPainter p(this); + + // Draw maximum values + p.setBrush(Qt::white); + p.setPen(Qt::black); + p.drawRect(centerx - max_size, centery - max_size, max_size * 2, max_size * 2); + + // Draw radius + p.setBrush(c_stick ? Qt::yellow : Qt::darkGray); + p.drawPolygon(radius_octagon.data(), static_cast(radius_octagon.size())); + + // Draw deadzone + p.setBrush(c_stick ? Qt::darkYellow : Qt::lightGray); + p.drawEllipse(centerx - deadzone * stick_size, centery - deadzone * stick_size, + deadzone * stick_size * 2, deadzone * stick_size * 2); + + // Draw stick + p.setBrush(Qt::black); + p.drawEllipse(centerx - 4 + curx * max_size, centery - 4 + cury * max_size, 8, 8); + + // Draw virtual stick + p.setBrush(Qt::red); + p.drawEllipse(centerx - 4 + virt_curx * max_size * radius, + centery - 4 + virt_cury * max_size * radius, 8, 8); +} + +void MappingIndicator::DrawMixedTriggers() +{ + QPainter p(this); + + // Polled values + double r_analog = PollControlState(m_mixed_triggers_r_analog); + double r_button = PollControlState(m_mixed_triggers_r_button); + double l_analog = PollControlState(m_mixed_triggers_l_analog); + double l_button = PollControlState(m_mixed_triggers_l_button); + double threshold = m_mixed_triggers_threshold->GetValue(); + + double r_bar_percent = r_analog; + double l_bar_percent = l_analog; + + if (r_button && (r_button != r_analog) || (r_button == r_analog) && (r_analog > threshold)) + r_bar_percent = 1; + else + r_bar_percent *= 0.8; + + if (l_button && (l_button != l_analog) || (l_button == l_analog) && (l_analog > threshold)) + l_bar_percent = 1; + else + l_bar_percent *= 0.8; + + p.fillRect(0, 0, width(), 64, Qt::black); + + p.fillRect(0, 0, l_bar_percent * width(), 32, Qt::red); + p.fillRect(0, 32, r_bar_percent * width(), 32, Qt::red); + + p.setPen(Qt::white); + p.drawLine(width() * 0.8, 0, width() * 0.8, 63); + p.drawLine(0, 32, width(), 32); + + p.setPen(Qt::green); + p.drawLine(width() * 0.8 * threshold, 0, width() * 0.8 * threshold, 63); + + p.setBrush(Qt::black); + p.setPen(Qt::white); + p.drawText(width() * 0.225, 16, tr("L-Analog")); + p.drawText(width() * 0.8 + 16, 16, tr("L")); + p.drawText(width() * 0.225, 48, tr("R-Analog")); + p.drawText(width() * 0.8 + 16, 48, tr("R")); +} + +void MappingIndicator::paintEvent(QPaintEvent*) +{ + switch (m_group->type) + { + case ControllerEmu::GroupType::Cursor: + DrawCursor(false); + break; + case ControllerEmu::GroupType::Tilt: + DrawCursor(true); + break; + case ControllerEmu::GroupType::Stick: + DrawStick(); + break; + case ControllerEmu::GroupType::MixedTriggers: + DrawMixedTriggers(); + break; + default: + break; + } +} diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingIndicator.h b/Source/Core/DolphinQt2/Config/Mapping/MappingIndicator.h new file mode 100644 index 0000000000..c563fb2880 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/Mapping/MappingIndicator.h @@ -0,0 +1,68 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +namespace ControllerEmu +{ +class Control; +class ControlGroup; +class NumericSetting; +} + +class QPaintEvent; +class QTimer; + +class ControlReference; + +class MappingIndicator : public QWidget +{ +public: + explicit MappingIndicator(ControllerEmu::ControlGroup* group); + +private: + void BindCursorControls(bool tilt); + void BindStickControls(); + void BindMixedTriggersControls(); + + void DrawCursor(bool tilt); + void DrawStick(); + void DrawMixedTriggers(); + + void paintEvent(QPaintEvent*) override; + ControllerEmu::ControlGroup* m_group; + + // Stick settings + ControlReference* m_stick_up; + ControlReference* m_stick_down; + ControlReference* m_stick_left; + ControlReference* m_stick_right; + ControlReference* m_stick_modifier; + + ControllerEmu::NumericSetting* m_stick_radius; + ControllerEmu::NumericSetting* m_stick_deadzone; + + // Cursor settings + ControlReference* m_cursor_up; + ControlReference* m_cursor_down; + ControlReference* m_cursor_left; + ControlReference* m_cursor_right; + ControlReference* m_cursor_forward; + ControlReference* m_cursor_backward; + + ControllerEmu::NumericSetting* m_cursor_center; + ControllerEmu::NumericSetting* m_cursor_width; + ControllerEmu::NumericSetting* m_cursor_height; + ControllerEmu::NumericSetting* m_cursor_deadzone; + + // Triggers settings + ControlReference* m_mixed_triggers_r_analog; + ControlReference* m_mixed_triggers_r_button; + ControlReference* m_mixed_triggers_l_analog; + ControlReference* m_mixed_triggers_l_button; + + ControllerEmu::NumericSetting* m_mixed_triggers_threshold; + + QTimer* m_timer; +}; diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt2/Config/Mapping/MappingWidget.cpp index 883d329473..5c2261de06 100644 --- a/Source/Core/DolphinQt2/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt2/Config/Mapping/MappingWidget.cpp @@ -11,6 +11,7 @@ #include "DolphinQt2/Config/Mapping/IOWindow.h" #include "DolphinQt2/Config/Mapping/MappingBool.h" #include "DolphinQt2/Config/Mapping/MappingButton.h" +#include "DolphinQt2/Config/Mapping/MappingIndicator.h" #include "DolphinQt2/Config/Mapping/MappingNumeric.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "InputCommon/ControlReference/ControlReference.h" @@ -47,9 +48,14 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con group_box->setLayout(form_layout); + bool need_indicator = group->type == ControllerEmu::GroupType::Cursor || + group->type == ControllerEmu::GroupType::Stick || + group->type == ControllerEmu::GroupType::Tilt || + group->type == ControllerEmu::GroupType::MixedTriggers; + for (auto& control : group->controls) { - auto* button = new MappingButton(this, control->control_ref.get()); + auto* button = new MappingButton(this, control->control_ref.get(), !need_indicator); button->setMinimumWidth(100); button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); @@ -87,6 +93,9 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con m_bools.push_back(checkbox); } + if (need_indicator) + form_layout->addRow(new MappingIndicator(group)); + return group_box; } diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index cb8410c289..3382d1f459 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -218,6 +218,7 @@ + @@ -294,6 +295,7 @@ + diff --git a/Source/Core/DolphinQt2/Host.cpp b/Source/Core/DolphinQt2/Host.cpp index e053cd91fa..e23f355884 100644 --- a/Source/Core/DolphinQt2/Host.cpp +++ b/Source/Core/DolphinQt2/Host.cpp @@ -10,6 +10,7 @@ #include "Common/Common.h" #include "Core/ConfigManager.h" #include "Core/Host.h" +#include "DolphinQt2/Settings.h" #include "VideoCommon/RenderBase.h" Host::Host() = default; @@ -108,7 +109,7 @@ void Host_RequestRenderWindowSize(int w, int h) } bool Host_UINeedsControllerState() { - return false; + return Settings::Instance().IsControllerStateNeeded(); } void Host_NotifyMapLoaded() { diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index d93a338f71..7809c1b2fa 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -269,3 +269,13 @@ bool Settings::IsBreakpointsVisible() const { return QSettings().value(QStringLiteral("debugger/showbreakpoints")).toBool(); } + +bool Settings::IsControllerStateNeeded() const +{ + return m_controller_state_needed; +} + +void Settings::SetControllerStateNeeded(bool needed) +{ + m_controller_state_needed = needed; +} diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h index 3cdc10959e..9a6950c784 100644 --- a/Source/Core/DolphinQt2/Settings.h +++ b/Source/Core/DolphinQt2/Settings.h @@ -46,6 +46,8 @@ public: void SetLogVisible(bool visible); bool IsLogConfigVisible() const; void SetLogConfigVisible(bool visible); + bool IsControllerStateNeeded() const; + void SetControllerStateNeeded(bool needed); // GameList QStringList GetPaths() const; @@ -111,6 +113,7 @@ signals: void DebugModeToggled(bool enabled); private: + bool m_controller_state_needed = false; std::unique_ptr m_client; std::unique_ptr m_server; Settings();