summaryrefslogtreecommitdiff
path: root/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2019-01-10 19:04:27 -0500
committerGitHub <noreply@github.com>2019-01-10 19:04:27 -0500
commit012338288e30e765e2ef8ec284cdbaceb42ba5ea (patch)
tree2874ac5d304b6e881673cc7b5e256bc85d1df4d4 /Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
parentc2afcb0f6bba761c248031ee715a1db3fd4dbe05 (diff)
parent7efa96eda979f5e717813e7c76cefb0720bc239a (diff)
Merge pull request #7662 from jordan-woyak/mapping-indicators-make-pretty
ControllerEmu: Make mapping indicators pretty
Diffstat (limited to 'Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp')
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp373
1 files changed, 223 insertions, 150 deletions
diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
index a09b41e5db..e0b44a2895 100644
--- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
+++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
@@ -14,174 +14,199 @@
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
-#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
+#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
+#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/Device.h"
#include "DolphinQt/Settings.h"
+// Color constants to keep things looking consistent:
+// TODO: could we maybe query theme colors from Qt for the bounding box?
+const QColor BBOX_PEN_COLOR = Qt::darkGray;
+const QColor BBOX_BRUSH_COLOR = Qt::white;
+
+const QColor RAW_INPUT_COLOR = Qt::darkGray;
+const QColor ADJ_INPUT_COLOR = Qt::red;
+const QPen INPUT_SHAPE_PEN(RAW_INPUT_COLOR, 1.0, Qt::DashLine);
+
+const QColor DEADZONE_COLOR = Qt::darkGray;
+const QBrush DEADZONE_BRUSH(DEADZONE_COLOR, Qt::BDiagPattern);
+
+const QColor TEXT_COLOR = Qt::darkGray;
+// Text color that is visible atop ADJ_INPUT_COLOR:
+const QColor TEXT_ALT_COLOR = Qt::white;
+
+const QColor STICK_GATE_COLOR = Qt::lightGray;
+const QColor C_STICK_GATE_COLOR = Qt::yellow;
+const QColor CURSOR_TV_COLOR = 0xaed6f1;
+const QColor TILT_GATE_COLOR = 0xa2d9ce;
+
+constexpr int INPUT_DOT_RADIUS = 2;
+
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:
- // Nothing needed:
- 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)
+namespace
{
- 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();
+// Constructs a polygon by querying a radius at varying angles:
+template <typename F>
+QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale)
+{
+ // A multiple of 8 (octagon) and enough points to be visibly pleasing:
+ constexpr int shape_point_count = 32;
+ QPolygonF shape{shape_point_count};
- if (!tilt)
+ int p = 0;
+ for (auto& point : shape)
{
- m_cursor_forward = m_group->controls[4]->control_ref.get();
- m_cursor_backward = m_group->controls[5]->control_ref.get();
+ const double angle = MathUtil::TAU * p / shape.size();
+ const double radius = radius_getter(angle) * scale;
- 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();
+ point = {std::cos(angle) * radius, std::sin(angle) * radius};
+ ++p;
}
-}
-
-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();
+ return shape;
}
+} // namespace
-static ControlState PollControlState(ControlReference* ref)
+void MappingIndicator::DrawCursor(ControllerEmu::Cursor& cursor)
{
+ const QColor tv_brush_color = CURSOR_TV_COLOR;
+ const QColor tv_pen_color = tv_brush_color.darker(125);
+
+ // TODO: This SetControllerStateNeeded interface leaks input into the game
+ // We should probably hold the mutex for UI updates.
Settings::Instance().SetControllerStateNeeded(true);
+ const auto raw_coord = cursor.GetState(false);
+ const auto adj_coord = cursor.GetState(true);
+ Settings::Instance().SetControllerStateNeeded(false);
- auto state = ref->State();
+ // Bounding box size:
+ const double scale = height() / 2.5;
- Settings::Instance().SetControllerStateNeeded(false);
+ QPainter p(this);
+ p.translate(width() / 2, height() / 2);
- if (state != 0)
- return state;
- else
- return 0;
-}
+ // Bounding box.
+ p.setBrush(BBOX_BRUSH_COLOR);
+ p.setPen(BBOX_PEN_COLOR);
+ p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
-void MappingIndicator::DrawCursor(bool tilt)
-{
- float centerx = width() / 2., centery = height() / 2.;
+ // UI y-axis is opposite that of stick.
+ p.scale(1.0, -1.0);
- QPainter p(this);
+ // Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
- float width = 64, height = 64;
- float deadzone = m_cursor_deadzone->GetValue() * 48;
-
- if (!tilt)
+ // Deadzone for Z (forward/backward):
+ const double deadzone = cursor.numeric_settings[cursor.SETTING_DEADZONE]->GetValue();
+ if (deadzone > 0.0)
{
- float depth = centery - PollControlState(m_cursor_forward) * this->height() / 2.5 +
- PollControlState(m_cursor_backward) * this->height() / 2.5;
+ p.setPen(DEADZONE_COLOR);
+ p.setBrush(DEADZONE_BRUSH);
+ p.drawRect(QRectF(-scale, -deadzone * scale, scale * 2, deadzone * scale * 2));
+ }
- p.fillRect(0, depth, this->width(), 4, Qt::gray);
+ // Raw Z:
+ p.setPen(Qt::NoPen);
+ p.setBrush(RAW_INPUT_COLOR);
+ p.drawRect(
+ QRectF(-scale, raw_coord.z * scale - INPUT_DOT_RADIUS / 2, scale * 2, INPUT_DOT_RADIUS));
- width *= m_cursor_width->GetValue();
- height *= m_cursor_height->GetValue();
+ // Adjusted Z (if not hidden):
+ if (adj_coord.z && adj_coord.x < 10000)
+ {
+ p.setBrush(ADJ_INPUT_COLOR);
+ p.drawRect(
+ QRectF(-scale, adj_coord.z * scale - INPUT_DOT_RADIUS / 2, scale * 2, INPUT_DOT_RADIUS));
}
- 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;
+ // TV screen or whatever you want to call this:
+ constexpr double tv_scale = 0.75;
+ constexpr double center_scale = 2.0 / 3.0;
- // Draw background
- p.setBrush(Qt::white);
- p.setPen(Qt::black);
- p.drawRect(centerx - (width / 2), centery - (height / 2), width, height);
+ const double tv_center = (cursor.numeric_settings[cursor.SETTING_CENTER]->GetValue() - 0.5);
+ const double tv_width = cursor.numeric_settings[cursor.SETTING_WIDTH]->GetValue();
+ const double tv_height = cursor.numeric_settings[cursor.SETTING_HEIGHT]->GetValue();
- // Draw deadzone
- p.setBrush(Qt::lightGray);
- p.drawEllipse(centerx - (deadzone / 2), centery - (deadzone / 2), deadzone, deadzone);
+ p.setPen(tv_pen_color);
+ p.setBrush(tv_brush_color);
+ auto gate_polygon = GetPolygonFromRadiusGetter(
+ [&cursor](double ang) { return cursor.GetGateRadiusAtAngle(ang); }, scale);
+ for (auto& pt : gate_polygon)
+ {
+ pt = {pt.x() * tv_width, pt.y() * tv_height + tv_center * center_scale * scale};
+ pt *= tv_scale;
+ }
+ p.drawPolygon(gate_polygon);
- // Draw cursor
- p.fillRect(curx, cury, 8, 8, Qt::red);
-}
+ // Deadzone.
+ p.setPen(DEADZONE_COLOR);
+ p.setBrush(DEADZONE_BRUSH);
+ p.drawPolygon(GetPolygonFromRadiusGetter(
+ [&cursor](double ang) { return cursor.GetDeadzoneRadiusAtAngle(ang); }, scale));
-// Constructs a polygon by querying a radius at varying angles:
-template <typename F>
-QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale)
-{
- // A multiple of 8 (octagon) and enough points to be visibly pleasing:
- constexpr int shape_point_count = 32;
- QPolygonF shape{shape_point_count};
+ // Input shape.
+ p.setPen(INPUT_SHAPE_PEN);
+ p.setBrush(Qt::NoBrush);
+ p.drawPolygon(GetPolygonFromRadiusGetter(
+ [&cursor](double ang) { return cursor.GetInputRadiusAtAngle(ang); }, scale));
- int p = 0;
- for (auto& point : shape)
- {
- const double angle = MathUtil::TAU * p / shape.size();
- const double radius = radius_getter(angle) * scale;
+ // Raw stick position.
+ p.setPen(Qt::NoPen);
+ p.setBrush(RAW_INPUT_COLOR);
+ p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
- point = {std::cos(angle) * radius, std::sin(angle) * radius};
- ++p;
+ // Adjusted cursor position (if not hidden):
+ if (adj_coord.x < 10000)
+ {
+ p.setPen(Qt::NoPen);
+ p.setBrush(ADJ_INPUT_COLOR);
+ const QPointF pt(adj_coord.x / 2.0, (adj_coord.y - tv_center) / 2.0 + tv_center * center_scale);
+ p.drawEllipse(pt * scale * tv_scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
-
- return shape;
}
-void MappingIndicator::DrawStick()
+void MappingIndicator::DrawReshapableInput(ControllerEmu::ReshapableInput& stick)
{
- // Make the c-stick yellow:
+ // Some hacks for pretty colors:
const bool is_c_stick = m_group->name == "C-Stick";
- const QColor gate_brush_color = is_c_stick ? Qt::yellow : Qt::lightGray;
- const QColor gate_pen_color = gate_brush_color.darker(125);
+ const bool is_tilt = m_group->name == "Tilt";
+
+ QColor gate_brush_color = STICK_GATE_COLOR;
- auto& stick = *static_cast<ControllerEmu::AnalogStick*>(m_group);
+ if (is_c_stick)
+ gate_brush_color = C_STICK_GATE_COLOR;
+ else if (is_tilt)
+ gate_brush_color = TILT_GATE_COLOR;
+
+ const QColor gate_pen_color = gate_brush_color.darker(125);
// TODO: This SetControllerStateNeeded interface leaks input into the game
// We should probably hold the mutex for UI updates.
Settings::Instance().SetControllerStateNeeded(true);
- const auto raw_coord = stick.GetState(false);
- const auto adj_coord = stick.GetState(true);
+ const auto raw_coord = stick.GetReshapableState(false);
+ const auto adj_coord = stick.GetReshapableState(true);
Settings::Instance().SetControllerStateNeeded(false);
// Bounding box size:
const double scale = height() / 2.5;
- const float dot_radius = 2;
-
QPainter p(this);
p.translate(width() / 2, height() / 2);
// Bounding box.
- p.setBrush(Qt::white);
- p.setPen(Qt::gray);
+ p.setBrush(BBOX_BRUSH_COLOR);
+ p.setPen(BBOX_PEN_COLOR);
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// UI y-axis is opposite that of stick.
@@ -198,76 +223,126 @@ void MappingIndicator::DrawStick()
[&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }, scale));
// Deadzone.
- p.setPen(Qt::darkGray);
- p.setBrush(QBrush(Qt::darkGray, Qt::BDiagPattern));
+ p.setPen(DEADZONE_COLOR);
+ p.setBrush(DEADZONE_BRUSH);
p.drawPolygon(GetPolygonFromRadiusGetter(
[&stick](double ang) { return stick.GetDeadzoneRadiusAtAngle(ang); }, scale));
// Input shape.
- p.setPen(QPen(Qt::darkGray, 1.0, Qt::DashLine));
+ p.setPen(INPUT_SHAPE_PEN);
p.setBrush(Qt::NoBrush);
p.drawPolygon(GetPolygonFromRadiusGetter(
[&stick](double ang) { return stick.GetInputRadiusAtAngle(ang); }, scale));
// Raw stick position.
p.setPen(Qt::NoPen);
- p.setBrush(Qt::darkGray);
- p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, dot_radius, dot_radius);
+ p.setBrush(RAW_INPUT_COLOR);
+ p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Adjusted stick position.
if (adj_coord.x || adj_coord.y)
{
p.setPen(Qt::NoPen);
- p.setBrush(Qt::red);
- p.drawEllipse(QPointF{adj_coord.x, adj_coord.y} * scale, dot_radius, dot_radius);
+ p.setBrush(ADJ_INPUT_COLOR);
+ p.drawEllipse(QPointF{adj_coord.x, adj_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
}
void MappingIndicator::DrawMixedTriggers()
{
QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::TextAntialiasing, true);
- p.setRenderHint(QPainter::SmoothPixmapTransform, true);
- // 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();
+ const auto& triggers = *static_cast<ControllerEmu::MixedTriggers*>(m_group);
+ const ControlState threshold = triggers.GetThreshold();
+ const ControlState deadzone = triggers.GetDeadzone();
- double r_bar_percent = r_analog;
- double l_bar_percent = l_analog;
+ // MixedTriggers interface is a bit ugly:
+ constexpr int TRIGGER_COUNT = 2;
+ std::array<ControlState, TRIGGER_COUNT> raw_analog_state;
+ std::array<ControlState, TRIGGER_COUNT> adj_analog_state;
+ const std::array<u16, TRIGGER_COUNT> button_masks = {0x1, 0x2};
+ u16 button_state = 0;
- if ((r_button && r_button != r_analog) || (r_button == r_analog && r_analog > threshold))
- r_bar_percent = 1;
- else
- r_bar_percent *= 0.8;
+ Settings::Instance().SetControllerStateNeeded(true);
+ triggers.GetState(&button_state, button_masks.data(), raw_analog_state.data(), false);
+ triggers.GetState(&button_state, button_masks.data(), adj_analog_state.data(), true);
+ Settings::Instance().SetControllerStateNeeded(false);
- if ((l_button && l_button != l_analog) || (l_button == l_analog && l_analog > threshold))
- l_bar_percent = 1;
- else
- l_bar_percent *= 0.8;
+ // Rectangle sizes:
+ const int trigger_height = 32;
+ const int trigger_width = width() - 1;
+ const int trigger_button_width = 32;
+ const int trigger_analog_width = trigger_width - trigger_button_width;
- p.fillRect(0, 0, width(), 64, Qt::black);
+ // Bounding box background:
+ p.setPen(Qt::NoPen);
+ p.setBrush(BBOX_BRUSH_COLOR);
+ p.drawRect(0, 0, trigger_width, trigger_height * TRIGGER_COUNT);
+
+ for (int t = 0; t != TRIGGER_COUNT; ++t)
+ {
+ const double raw_analog = raw_analog_state[t];
+ const double adj_analog = adj_analog_state[t];
+ const bool trigger_button = button_state & button_masks[t];
+ auto const analog_name = QString::fromStdString(triggers.controls[TRIGGER_COUNT + t]->ui_name);
+ auto const button_name = QString::fromStdString(triggers.controls[t]->ui_name);
- p.fillRect(0, 0, l_bar_percent * width(), 32, Qt::red);
- p.fillRect(0, 32, r_bar_percent * width(), 32, Qt::red);
+ const QRectF trigger_rect(0, 0, trigger_width, trigger_height);
- p.setPen(Qt::white);
- p.drawLine(width() * 0.8, 0, width() * 0.8, 63);
- p.drawLine(0, 32, width(), 32);
+ const QRectF analog_rect(0, 0, trigger_analog_width, trigger_height);
- p.setPen(Qt::green);
- p.drawLine(width() * 0.8 * threshold, 0, width() * 0.8 * threshold, 63);
+ // Unactivated analog text:
+ p.setPen(TEXT_COLOR);
+ p.drawText(analog_rect, Qt::AlignCenter, analog_name);
- p.setBrush(Qt::black);
- p.setPen(Qt::white);
- p.drawText(width() * 0.225, 20, tr("L-Analog"));
- p.drawText(width() * 0.8 + 16, 20, tr("L"));
- p.drawText(width() * 0.225, 52, tr("R-Analog"));
- p.drawText(width() * 0.8 + 16, 52, tr("R"));
+ const QRectF adj_analog_rect(0, 0, adj_analog * trigger_analog_width, trigger_height);
+
+ // Trigger analog:
+ p.setPen(Qt::NoPen);
+ p.setBrush(RAW_INPUT_COLOR);
+ p.drawEllipse(QPoint(raw_analog * trigger_analog_width, trigger_height - INPUT_DOT_RADIUS),
+ INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
+ p.setBrush(ADJ_INPUT_COLOR);
+ p.drawRect(adj_analog_rect);
+
+ // Deadzone:
+ p.setPen(DEADZONE_COLOR);
+ p.setBrush(DEADZONE_BRUSH);
+ p.drawRect(0, 0, trigger_analog_width * deadzone, trigger_height);
+
+ // Threshold setting:
+ const int threshold_x = trigger_analog_width * threshold;
+ p.setPen(INPUT_SHAPE_PEN);
+ p.drawLine(threshold_x, 0, threshold_x, trigger_height);
+
+ const QRectF button_rect(trigger_analog_width, 0, trigger_button_width, trigger_height);
+
+ // Trigger button:
+ p.setPen(BBOX_PEN_COLOR);
+ p.setBrush(trigger_button ? ADJ_INPUT_COLOR : BBOX_BRUSH_COLOR);
+ p.drawRect(button_rect);
+
+ // Bounding box outline:
+ p.setPen(BBOX_PEN_COLOR);
+ p.setBrush(Qt::NoBrush);
+ p.drawRect(trigger_rect);
+
+ // Button text:
+ p.setPen(TEXT_COLOR);
+ p.setPen(trigger_button ? TEXT_ALT_COLOR : TEXT_COLOR);
+ p.drawText(button_rect, Qt::AlignCenter, button_name);
+
+ // Activated analog text:
+ p.setPen(TEXT_ALT_COLOR);
+ p.setClipping(true);
+ p.setClipRect(adj_analog_rect);
+ p.drawText(analog_rect, Qt::AlignCenter, analog_name);
+ p.setClipping(false);
+
+ // Move down for next trigger:
+ p.translate(0.0, trigger_height);
+ }
}
void MappingIndicator::paintEvent(QPaintEvent*)
@@ -275,13 +350,11 @@ void MappingIndicator::paintEvent(QPaintEvent*)
switch (m_group->type)
{
case ControllerEmu::GroupType::Cursor:
- DrawCursor(false);
- break;
- case ControllerEmu::GroupType::Tilt:
- DrawCursor(true);
+ DrawCursor(*static_cast<ControllerEmu::Cursor*>(m_group));
break;
case ControllerEmu::GroupType::Stick:
- DrawStick();
+ case ControllerEmu::GroupType::Tilt:
+ DrawReshapableInput(*static_cast<ControllerEmu::ReshapableInput*>(m_group));
break;
case ControllerEmu::GroupType::MixedTriggers:
DrawMixedTriggers();