Merge pull request #8643 from jordan-woyak/mapping-race-fix

InputCommon/DolphinQt: Mapping related race fixes and cleanups.
This commit is contained in:
spycrab 2020-02-25 22:57:00 +01:00 committed by GitHub
commit 459b47295d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 392 additions and 470 deletions

View File

@ -544,7 +544,7 @@ void InputStateDelegate::paint(QPainter* painter, const QStyleOptionViewItem& op
rect.setWidth(rect.width() * std::clamp(state, 0.0, 1.0)); rect.setWidth(rect.width() * std::clamp(state, 0.0, 1.0));
// Create a temporary indicator object to retreive color constants. // Create a temporary indicator object to retreive color constants.
MappingIndicator indicator(nullptr); MappingIndicator indicator;
painter->save(); painter->save();

View File

@ -37,11 +37,32 @@ const QColor TILT_GATE_COLOR = 0xa2d9ce;
const QColor SWING_GATE_COLOR = 0xcea2d9; const QColor SWING_GATE_COLOR = 0xcea2d9;
constexpr int INPUT_DOT_RADIUS = 2; constexpr int INPUT_DOT_RADIUS = 2;
constexpr int NORMAL_INDICATOR_WIDTH = 100;
constexpr int NORMAL_INDICATOR_HEIGHT = 100;
constexpr int NORMAL_INDICATOR_PADDING = 2;
// Per trigger.
constexpr int TRIGGER_INDICATOR_HEIGHT = 32;
QPen GetCosmeticPen(QPen pen)
{
pen.setCosmetic(true);
return pen;
}
QPen GetInputDotPen(QPen pen)
{
pen.setWidth(INPUT_DOT_RADIUS * 2);
pen.setCapStyle(Qt::PenCapStyle::RoundCap);
return GetCosmeticPen(pen);
}
} // namespace } // namespace
QPen MappingIndicator::GetBBoxPen() const QPen MappingIndicator::GetBBoxPen() const
{ {
return palette().shadow().color(); return QPen(palette().shadow().color(), 0);
} }
QBrush MappingIndicator::GetBBoxBrush() const QBrush MappingIndicator::GetBBoxBrush() const
@ -51,18 +72,18 @@ QBrush MappingIndicator::GetBBoxBrush() const
QColor MappingIndicator::GetRawInputColor() const QColor MappingIndicator::GetRawInputColor() const
{ {
return palette().shadow().color(); QColor color = palette().text().color();
color.setAlphaF(0.5);
return color;
} }
QPen MappingIndicator::GetInputShapePen() const QPen MappingIndicator::GetInputShapePen() const
{ {
return QPen{GetRawInputColor(), 1.0, Qt::DashLine}; return QPen{GetRawInputColor(), 0.0, Qt::DashLine};
} }
QColor MappingIndicator::GetAdjustedInputColor() const QColor MappingIndicator::GetAdjustedInputColor() const
{ {
// Using highlight color works (typically blue) but the contrast is pretty low.
// return palette().highlight().color();
return Qt::red; return Qt::red;
} }
@ -80,12 +101,14 @@ QColor MappingIndicator::GetDeadZoneColor() const
QPen MappingIndicator::GetDeadZonePen() const QPen MappingIndicator::GetDeadZonePen() const
{ {
return GetDeadZoneColor(); return QPen(GetDeadZoneColor(), 0);
} }
QBrush MappingIndicator::GetDeadZoneBrush() const QBrush MappingIndicator::GetDeadZoneBrush(QPainter& painter) const
{ {
return QBrush{GetDeadZoneColor(), Qt::BDiagPattern}; QBrush brush{GetDeadZoneColor(), Qt::FDiagPattern};
brush.setTransform(painter.transform().inverted());
return brush;
} }
QColor MappingIndicator::GetTextColor() const QColor MappingIndicator::GetTextColor() const
@ -105,20 +128,17 @@ void MappingIndicator::AdjustGateColor(QColor* color)
color->setHsvF(color->hueF(), color->saturationF(), 1 - color->valueF()); color->setHsvF(color->hueF(), color->saturationF(), 1 - color->valueF());
} }
MappingIndicator::MappingIndicator(ControllerEmu::ControlGroup* group) : m_group(group) SquareIndicator::SquareIndicator()
{ {
// TODO: Make these magic numbers less ugly. // Additional pixel for border.
int required_height = 106; setFixedWidth(NORMAL_INDICATOR_WIDTH + (NORMAL_INDICATOR_PADDING + 1) * 2);
setFixedHeight(NORMAL_INDICATOR_HEIGHT + (NORMAL_INDICATOR_PADDING + 1) * 2);
if (group && ControllerEmu::GroupType::MixedTriggers == group->type)
required_height = 64 + 1;
setFixedHeight(required_height);
} }
double MappingIndicator::GetScale() const MixedTriggersIndicator::MixedTriggersIndicator(ControllerEmu::MixedTriggers& group) : m_group(group)
{ {
return height() / 2 - 2; setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Ignored);
setFixedHeight(TRIGGER_INDICATOR_HEIGHT * int(group.GetTriggerCount()) + 1);
} }
namespace namespace
@ -129,8 +149,7 @@ constexpr int SPHERE_POINT_COUNT = 200;
// Constructs a polygon by querying a radius at varying angles: // Constructs a polygon by querying a radius at varying angles:
template <typename F> template <typename F>
QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale, QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, Common::DVec2 center = {0.0, 0.0})
Common::DVec2 center = {0.0, 0.0})
{ {
// A multiple of 8 (octagon) and enough points to be visibly pleasing: // A multiple of 8 (octagon) and enough points to be visibly pleasing:
constexpr int shape_point_count = 32; constexpr int shape_point_count = 32;
@ -140,10 +159,9 @@ QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter, double scale,
for (auto& point : shape) for (auto& point : shape)
{ {
const double angle = MathUtil::TAU * p / shape.size(); const double angle = MathUtil::TAU * p / shape.size();
const double radius = radius_getter(angle) * scale; const double radius = radius_getter(angle);
point = {std::cos(angle) * radius + center.x * scale, point = {std::cos(angle) * radius + center.x, std::sin(angle) * radius + center.y};
std::sin(angle) * radius + center.y * scale};
++p; ++p;
} }
@ -209,194 +227,142 @@ void GenerateFibonacciSphere(int point_count, F&& callback)
} // namespace } // namespace
void MappingIndicator::DrawCursor(ControllerEmu::Cursor& cursor) void MappingIndicator::paintEvent(QPaintEvent*)
{ {
const auto center = cursor.GetCenter(); const auto lock = ControllerEmu::EmulatedController::GetStateLock();
Draw();
}
QColor tv_brush_color = CURSOR_TV_COLOR; void CursorIndicator::Draw()
QColor tv_pen_color = tv_brush_color.darker(125); {
const auto adj_coord = m_cursor_group.GetState(true);
AdjustGateColor(&tv_brush_color); DrawReshapableInput(m_cursor_group, CURSOR_TV_COLOR,
AdjustGateColor(&tv_pen_color); adj_coord.IsVisible() ?
std::make_optional(Common::DVec2(adj_coord.x, adj_coord.y)) :
std::nullopt);
}
const auto raw_coord = cursor.GetState(false); qreal SquareIndicator::GetContentsScale() const
const auto adj_coord = cursor.GetState(true); {
return (NORMAL_INDICATOR_WIDTH - 1.0) / 2;
}
UpdateCalibrationWidget({raw_coord.x, raw_coord.y}); void SquareIndicator::DrawBoundingBox(QPainter& p)
{
// Bounding box size:
const double scale = GetScale();
QPainter p(this);
p.translate(width() / 2, height() / 2);
// Bounding box.
p.setBrush(GetBBoxBrush()); p.setBrush(GetBBoxBrush());
p.setPen(GetBBoxPen()); p.setPen(GetBBoxPen());
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1); p.drawRect(QRectF{NORMAL_INDICATOR_PADDING + 0.5, NORMAL_INDICATOR_PADDING + 0.5,
NORMAL_INDICATOR_WIDTH + 1.0, NORMAL_INDICATOR_HEIGHT + 1.0});
}
// UI y-axis is opposite that of stick. void SquareIndicator::TransformPainter(QPainter& p)
p.scale(1.0, -1.0); {
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true); p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true); p.setRenderHint(QPainter::SmoothPixmapTransform, true);
if (IsCalibrating()) p.translate(width() / 2, height() / 2);
{ const auto scale = GetContentsScale();
DrawCalibration(p, {raw_coord.x, raw_coord.y}); p.scale(scale, scale);
return;
}
// TV screen or whatever you want to call this:
constexpr double TV_SCALE = 0.75;
p.setPen(tv_pen_color);
p.setBrush(tv_brush_color);
p.drawPolygon(GetPolygonFromRadiusGetter(
[&cursor](double ang) { return cursor.GetGateRadiusAtAngle(ang); }, scale * TV_SCALE));
// Deadzone.
p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush());
p.drawPolygon(GetPolygonFromRadiusGetter(
[&cursor](double ang) { return cursor.GetDeadzoneRadiusAtAngle(ang); }, scale, center));
// Input shape.
p.setPen(GetInputShapePen());
p.setBrush(Qt::NoBrush);
p.drawPolygon(GetPolygonFromRadiusGetter(
[&cursor](double ang) { return cursor.GetInputRadiusAtAngle(ang); }, scale, center));
// Center.
if (center.x || center.y)
{
p.setPen(Qt::NoPen);
p.setBrush(GetCenterColor());
p.drawEllipse(QPointF{center.x, center.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
// Raw stick position.
p.setPen(Qt::NoPen);
p.setBrush(GetRawInputColor());
p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Adjusted cursor position (if not hidden):
if (adj_coord.IsVisible())
{
p.setPen(Qt::NoPen);
p.setBrush(GetAdjustedInputColor());
p.drawEllipse(QPointF{adj_coord.x, adj_coord.y} * scale * TV_SCALE, INPUT_DOT_RADIUS,
INPUT_DOT_RADIUS);
}
} }
void MappingIndicator::DrawReshapableInput(ControllerEmu::ReshapableInput& stick) void ReshapableInputIndicator::DrawReshapableInput(
ControllerEmu::ReshapableInput& stick, QColor gate_brush_color,
std::optional<ControllerEmu::ReshapableInput::ReshapeData> adj_coord)
{ {
// Some hacks for pretty colors: QPainter p(this);
const bool is_c_stick = m_group->name == "C-Stick"; DrawBoundingBox(p);
const bool is_tilt = m_group->name == "Tilt"; TransformPainter(p);
const auto center = stick.GetCenter(); // UI y-axis is opposite that of stick.
p.scale(1.0, -1.0);
QColor gate_brush_color = STICK_GATE_COLOR;
if (is_c_stick)
gate_brush_color = C_STICK_GATE_COLOR;
else if (is_tilt)
gate_brush_color = TILT_GATE_COLOR;
QColor gate_pen_color = gate_brush_color.darker(125);
AdjustGateColor(&gate_brush_color);
AdjustGateColor(&gate_pen_color);
const auto raw_coord = stick.GetReshapableState(false); const auto raw_coord = stick.GetReshapableState(false);
Common::DVec2 adj_coord;
if (is_tilt)
{
WiimoteEmu::EmulateTilt(&m_motion_state, static_cast<ControllerEmu::Tilt*>(&stick),
1.f / INDICATOR_UPDATE_FREQ);
adj_coord = Common::DVec2{-m_motion_state.angle.y, m_motion_state.angle.x} / MathUtil::PI;
}
else
{
adj_coord = stick.GetReshapableState(true);
}
UpdateCalibrationWidget(raw_coord); UpdateCalibrationWidget(raw_coord);
// Bounding box size:
const double scale = GetScale();
QPainter p(this);
p.translate(width() / 2, height() / 2);
// Bounding box.
p.setBrush(GetBBoxBrush());
p.setPen(GetBBoxPen());
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// UI y-axis is opposite that of stick.
p.scale(1.0, -1.0);
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
if (IsCalibrating()) if (IsCalibrating())
{ {
DrawCalibration(p, raw_coord); DrawCalibration(p, raw_coord);
return; return;
} }
DrawUnderGate(p);
QColor gate_pen_color = gate_brush_color.darker(125);
AdjustGateColor(&gate_brush_color);
AdjustGateColor(&gate_pen_color);
// Input gate. (i.e. the octagon shape) // Input gate. (i.e. the octagon shape)
p.setPen(gate_pen_color); p.setPen(QPen(gate_pen_color, 0));
p.setBrush(gate_brush_color); p.setBrush(gate_brush_color);
p.drawPolygon(GetPolygonFromRadiusGetter( p.drawPolygon(
[&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }, scale)); GetPolygonFromRadiusGetter([&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }));
const auto center = stick.GetCenter();
// Deadzone. // Deadzone.
p.setPen(GetDeadZonePen()); p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush()); p.setBrush(GetDeadZoneBrush(p));
p.drawPolygon(GetPolygonFromRadiusGetter( p.drawPolygon(GetPolygonFromRadiusGetter(
[&stick](double ang) { return stick.GetDeadzoneRadiusAtAngle(ang); }, scale, center)); [&stick](double ang) { return stick.GetDeadzoneRadiusAtAngle(ang); }, center));
// Input shape. // Input shape.
p.setPen(GetInputShapePen()); p.setPen(GetInputShapePen());
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.drawPolygon(GetPolygonFromRadiusGetter( p.drawPolygon(GetPolygonFromRadiusGetter(
[&stick](double ang) { return stick.GetInputRadiusAtAngle(ang); }, scale, center)); [&stick](double ang) { return stick.GetInputRadiusAtAngle(ang); }, center));
// Center. // Center.
if (center.x || center.y) if (center.x || center.y)
{ {
p.setPen(Qt::NoPen); p.setPen(GetInputDotPen(GetCenterColor()));
p.setBrush(GetCenterColor()); p.drawPoint(QPointF{center.x, center.y});
p.drawEllipse(QPointF{center.x, center.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
} }
// Raw stick position. // Raw stick position.
p.setPen(Qt::NoPen); p.setPen(GetInputDotPen(GetRawInputColor()));
p.setBrush(GetRawInputColor()); p.drawPoint(QPointF{raw_coord.x, raw_coord.y});
p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Adjusted stick position. // Adjusted stick position.
if (adj_coord.x || adj_coord.y) if (adj_coord)
{ {
p.setPen(Qt::NoPen); p.setPen(GetInputDotPen(GetAdjustedInputColor()));
p.setBrush(GetAdjustedInputColor()); p.drawPoint(QPointF{adj_coord->x, adj_coord->y});
p.drawEllipse(QPointF{adj_coord.x, adj_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
} }
} }
void MappingIndicator::DrawMixedTriggers() void AnalogStickIndicator::Draw()
{
// Some hacks for pretty colors:
const bool is_c_stick = m_group.name == "C-Stick";
const auto gate_brush_color = is_c_stick ? C_STICK_GATE_COLOR : STICK_GATE_COLOR;
const auto adj_coord = m_group.GetReshapableState(true);
DrawReshapableInput(m_group, gate_brush_color,
(adj_coord.x || adj_coord.y) ? std::make_optional(adj_coord) : std::nullopt);
}
void TiltIndicator::Draw()
{
WiimoteEmu::EmulateTilt(&m_motion_state, &m_group, 1.f / INDICATOR_UPDATE_FREQ);
const auto adj_coord =
Common::DVec2{-m_motion_state.angle.y, m_motion_state.angle.x} / MathUtil::PI;
DrawReshapableInput(m_group, TILT_GATE_COLOR,
(adj_coord.x || adj_coord.y) ? std::make_optional(adj_coord) : std::nullopt);
}
void MixedTriggersIndicator::Draw()
{ {
QPainter p(this); QPainter p(this);
p.setRenderHint(QPainter::TextAntialiasing, true); p.setRenderHint(QPainter::TextAntialiasing, true);
const auto& triggers = *static_cast<ControllerEmu::MixedTriggers*>(m_group); const auto& triggers = m_group;
const ControlState threshold = triggers.GetThreshold(); const ControlState threshold = triggers.GetThreshold();
const ControlState deadzone = triggers.GetDeadzone(); const ControlState deadzone = triggers.GetDeadzone();
@ -411,15 +377,15 @@ void MappingIndicator::DrawMixedTriggers()
triggers.GetState(&button_state, button_masks.data(), adj_analog_state.data(), true); triggers.GetState(&button_state, button_masks.data(), adj_analog_state.data(), true);
// Rectangle sizes: // Rectangle sizes:
const int trigger_height = 32; const int trigger_height = TRIGGER_INDICATOR_HEIGHT;
const int trigger_width = width() - 1; const int trigger_width = width() - 1;
const int trigger_button_width = 32; const int trigger_button_width = trigger_height;
const int trigger_analog_width = trigger_width - trigger_button_width; const int trigger_analog_width = trigger_width - trigger_button_width;
// Bounding box background: // Bounding box background:
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(GetBBoxBrush()); p.setBrush(GetBBoxBrush());
p.drawRect(0, 0, trigger_width, trigger_height * TRIGGER_COUNT); p.drawRect(QRectF(0.5, 0.5, trigger_width, trigger_height * TRIGGER_COUNT));
for (int t = 0; t != TRIGGER_COUNT; ++t) for (int t = 0; t != TRIGGER_COUNT; ++t)
{ {
@ -429,35 +395,35 @@ void MappingIndicator::DrawMixedTriggers()
auto const analog_name = QString::fromStdString(triggers.controls[TRIGGER_COUNT + t]->ui_name); auto const analog_name = QString::fromStdString(triggers.controls[TRIGGER_COUNT + t]->ui_name);
auto const button_name = QString::fromStdString(triggers.controls[t]->ui_name); auto const button_name = QString::fromStdString(triggers.controls[t]->ui_name);
const QRectF trigger_rect(0, 0, trigger_width, trigger_height); const QRectF trigger_rect(0.5, 0.5, trigger_width, trigger_height);
const QRectF analog_rect(0, 0, trigger_analog_width, trigger_height); const QRectF analog_rect(0.5, 0.5, trigger_analog_width, trigger_height);
// Unactivated analog text: // Unactivated analog text:
p.setPen(GetTextColor()); p.setPen(GetTextColor());
p.drawText(analog_rect, Qt::AlignCenter, analog_name); p.drawText(analog_rect, Qt::AlignCenter, analog_name);
const QRectF adj_analog_rect(0, 0, adj_analog * trigger_analog_width, trigger_height); const QRectF adj_analog_rect(0.5, 0.5, adj_analog * trigger_analog_width, trigger_height);
// Trigger analog: // Trigger analog:
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(GetRawInputColor());
p.drawEllipse(QPoint(raw_analog * trigger_analog_width, trigger_height - INPUT_DOT_RADIUS),
INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
p.setBrush(GetAdjustedInputColor()); p.setBrush(GetAdjustedInputColor());
p.drawRect(adj_analog_rect); p.drawRect(adj_analog_rect);
p.setPen(GetInputDotPen(GetRawInputColor()));
p.drawPoint(QPoint(raw_analog * trigger_analog_width, trigger_height - INPUT_DOT_RADIUS));
// Deadzone: // Deadzone:
p.setPen(GetDeadZonePen()); p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush()); p.setBrush(GetDeadZoneBrush(p));
p.drawRect(0, 0, trigger_analog_width * deadzone, trigger_height); p.drawRect(QRectF(1.5, 1.5, trigger_analog_width * deadzone, trigger_height - 2));
// Threshold setting: // Threshold setting:
const int threshold_x = trigger_analog_width * threshold; const int threshold_x = trigger_analog_width * threshold;
p.setPen(GetInputShapePen()); p.setPen(GetInputShapePen());
p.drawLine(threshold_x, 0, threshold_x, trigger_height); p.drawLine(threshold_x, 0, threshold_x, trigger_height);
const QRectF button_rect(trigger_analog_width, 0, trigger_button_width, trigger_height); const QRectF button_rect(trigger_analog_width + 0.5, 0.5, trigger_button_width, trigger_height);
// Trigger button: // Trigger button:
p.setPen(GetBBoxPen()); p.setPen(GetBBoxPen());
@ -486,150 +452,51 @@ void MappingIndicator::DrawMixedTriggers()
} }
} }
void MappingIndicator::DrawForce(ControllerEmu::Force& force) void SwingIndicator::DrawUnderGate(QPainter& p)
{ {
const auto center = force.GetCenter(); auto& force = m_swing_group;
QColor gate_brush_color = SWING_GATE_COLOR;
QColor gate_pen_color = gate_brush_color.darker(125);
AdjustGateColor(&gate_brush_color);
AdjustGateColor(&gate_pen_color);
const auto raw_coord = force.GetState(false);
WiimoteEmu::EmulateSwing(&m_motion_state, &force, 1.f / INDICATOR_UPDATE_FREQ);
const auto& adj_coord = m_motion_state.position;
UpdateCalibrationWidget({raw_coord.x, raw_coord.y});
// Bounding box size:
const double scale = GetScale();
QPainter p(this);
p.translate(width() / 2, height() / 2);
// Bounding box.
p.setBrush(GetBBoxBrush());
p.setPen(GetBBoxPen());
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// UI y-axis is opposite that of stick.
p.scale(1.0, -1.0);
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
if (IsCalibrating())
{
DrawCalibration(p, {raw_coord.x, raw_coord.y});
return;
}
// Deadzone for Z (forward/backward): // Deadzone for Z (forward/backward):
const double deadzone = force.GetDeadzonePercentage(); const double deadzone = force.GetDeadzonePercentage();
if (deadzone > 0.0) if (deadzone > 0.0)
{ {
p.setPen(GetDeadZonePen()); p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush()); p.setBrush(GetDeadZoneBrush(p));
p.drawRect(QRectF(-scale, -deadzone * scale, scale * 2, deadzone * scale * 2)); p.drawRect(QRectF(-1, -deadzone, 2, deadzone * 2));
} }
// Raw Z: // Raw Z:
p.setPen(Qt::NoPen); const auto raw_coord = force.GetState(false);
p.setBrush(GetRawInputColor()); p.setPen(GetCosmeticPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS)));
p.drawRect( p.drawLine(QLineF(-1, raw_coord.z, 1, raw_coord.z));
QRectF(-scale, raw_coord.z * scale - INPUT_DOT_RADIUS / 2, scale * 2, INPUT_DOT_RADIUS));
// Adjusted Z: // Adjusted Z:
const auto& adj_coord = m_motion_state.position;
const auto curve_point = const auto curve_point =
std::max(std::abs(m_motion_state.angle.x), std::abs(m_motion_state.angle.z)) / MathUtil::TAU; std::max(std::abs(m_motion_state.angle.x), std::abs(m_motion_state.angle.z)) / MathUtil::TAU;
if (adj_coord.y || curve_point) if (adj_coord.y || curve_point)
{ {
// Show off the angle somewhat with a curved line. // Show off the angle somewhat with a curved line.
QPainterPath path; QPainterPath path;
path.moveTo(-scale, (adj_coord.y + curve_point) * -scale); path.moveTo(-1.0, (adj_coord.y + curve_point) * -1);
path.quadTo({0, (adj_coord.y - curve_point) * -scale}, path.quadTo({0, (adj_coord.y - curve_point) * -1}, {1, (adj_coord.y + curve_point) * -1});
{scale, (adj_coord.y + curve_point) * -scale});
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS)); p.setPen(GetCosmeticPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS)));
p.drawPath(path); p.drawPath(path);
} }
// Draw "gate" shape.
p.setPen(gate_pen_color);
p.setBrush(gate_brush_color);
p.drawPolygon(GetPolygonFromRadiusGetter(
[&force](double ang) { return force.GetGateRadiusAtAngle(ang); }, scale));
// Deadzone.
p.setPen(GetDeadZoneColor());
p.setBrush(GetDeadZoneBrush());
p.drawPolygon(GetPolygonFromRadiusGetter(
[&force](double ang) { return force.GetDeadzoneRadiusAtAngle(ang); }, scale, center));
// Input shape.
p.setPen(GetInputShapePen());
p.setBrush(Qt::NoBrush);
p.drawPolygon(GetPolygonFromRadiusGetter(
[&force](double ang) { return force.GetInputRadiusAtAngle(ang); }, scale, center));
// Center.
if (center.x || center.y)
{
p.setPen(Qt::NoPen);
p.setBrush(GetCenterColor());
p.drawEllipse(QPointF{center.x, center.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
// Raw stick position.
p.setPen(Qt::NoPen);
p.setBrush(GetRawInputColor());
p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Adjusted position:
if (adj_coord.x || adj_coord.z)
{
p.setPen(Qt::NoPen);
p.setBrush(GetAdjustedInputColor());
p.drawEllipse(QPointF{-adj_coord.x, adj_coord.z} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
} }
void MappingIndicator::paintEvent(QPaintEvent*) void SwingIndicator::Draw()
{ {
switch (m_group->type) auto& force = m_swing_group;
{ WiimoteEmu::EmulateSwing(&m_motion_state, &force, 1.f / INDICATOR_UPDATE_FREQ);
case ControllerEmu::GroupType::Cursor:
DrawCursor(*static_cast<ControllerEmu::Cursor*>(m_group)); DrawReshapableInput(force, SWING_GATE_COLOR,
break; Common::DVec2{-m_motion_state.position.x, m_motion_state.position.z});
case ControllerEmu::GroupType::Stick:
case ControllerEmu::GroupType::Tilt:
DrawReshapableInput(*static_cast<ControllerEmu::ReshapableInput*>(m_group));
break;
case ControllerEmu::GroupType::MixedTriggers:
DrawMixedTriggers();
break;
case ControllerEmu::GroupType::Force:
DrawForce(*static_cast<ControllerEmu::Force*>(m_group));
break;
default:
break;
}
} }
ShakeMappingIndicator::ShakeMappingIndicator(ControllerEmu::Shake* group) void ShakeMappingIndicator::Draw()
: MappingIndicator(group), m_shake_group(*group)
{
}
void ShakeMappingIndicator::paintEvent(QPaintEvent*)
{
DrawShake();
}
void ShakeMappingIndicator::DrawShake()
{ {
constexpr std::size_t HISTORY_COUNT = INDICATOR_UPDATE_FREQ; constexpr std::size_t HISTORY_COUNT = INDICATOR_UPDATE_FREQ;
@ -642,37 +509,24 @@ void ShakeMappingIndicator::DrawShake()
if (m_position_samples.size() > HISTORY_COUNT + 1) if (m_position_samples.size() > HISTORY_COUNT + 1)
m_position_samples.pop_back(); m_position_samples.pop_back();
// Bounding box size:
const double scale = GetScale();
QPainter p(this); QPainter p(this);
p.translate(width() / 2, height() / 2); DrawBoundingBox(p);
TransformPainter(p);
// Bounding box.
p.setBrush(GetBBoxBrush());
p.setPen(GetBBoxPen());
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// UI y-axis is opposite that of acceleration Z. // UI y-axis is opposite that of acceleration Z.
p.scale(1.0, -1.0); p.scale(1.0, -1.0);
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
// Deadzone. // Deadzone.
p.setPen(GetDeadZonePen()); p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush()); p.setBrush(GetDeadZoneBrush(p));
p.drawRect(-scale, 0, scale * 2, m_shake_group.GetDeadzone() * scale); p.drawRect(-1.0, 0, 2, m_shake_group.GetDeadzone());
// Raw input. // Raw input.
const auto raw_coord = m_shake_group.GetState(false); const auto raw_coord = m_shake_group.GetState(false);
p.setPen(Qt::NoPen); p.setPen(GetInputDotPen(GetRawInputColor()));
p.setBrush(GetRawInputColor());
for (std::size_t c = 0; c != raw_coord.data.size(); ++c) for (std::size_t c = 0; c != raw_coord.data.size(); ++c)
{ {
p.drawEllipse(QPointF{-0.5 + c * 0.5, raw_coord.data[c]} * scale, INPUT_DOT_RADIUS, p.drawPoint(QPointF{-0.5 + c * 0.5, raw_coord.data[c]});
INPUT_DOT_RADIUS);
} }
// Grid line. // Grid line.
@ -684,8 +538,8 @@ void ShakeMappingIndicator::DrawShake()
m_grid_line_position = (m_grid_line_position + 1) % HISTORY_COUNT; m_grid_line_position = (m_grid_line_position + 1) % HISTORY_COUNT;
} }
const double grid_line_x = 1.0 - m_grid_line_position * 2.0 / HISTORY_COUNT; const double grid_line_x = 1.0 - m_grid_line_position * 2.0 / HISTORY_COUNT;
p.setPen(GetRawInputColor()); p.setPen(QPen(GetRawInputColor(), 0));
p.drawLine(QPointF{grid_line_x, -1.0} * scale, QPointF{grid_line_x, 1.0} * scale); p.drawLine(QPointF{grid_line_x, -1.0}, QPointF{grid_line_x, 1.0});
// Position history. // Position history.
const QColor component_colors[] = {Qt::blue, Qt::green, Qt::red}; const QColor component_colors[] = {Qt::blue, Qt::green, Qt::red};
@ -697,60 +551,44 @@ void ShakeMappingIndicator::DrawShake()
int i = 0; int i = 0;
for (auto& sample : m_position_samples) for (auto& sample : m_position_samples)
{ {
polyline.append(QPointF{1.0 - i * 2.0 / HISTORY_COUNT, sample.data[c]} * scale); polyline.append(QPointF{1.0 - i * 2.0 / HISTORY_COUNT, sample.data[c]});
++i; ++i;
} }
p.setPen(component_colors[c]); p.setPen(QPen(component_colors[c], 0));
p.drawPolyline(polyline); p.drawPolyline(polyline);
} }
} }
AccelerometerMappingIndicator::AccelerometerMappingIndicator(ControllerEmu::IMUAccelerometer* group) void AccelerometerMappingIndicator::Draw()
: MappingIndicator(group), m_accel_group(*group)
{
}
void AccelerometerMappingIndicator::paintEvent(QPaintEvent*)
{ {
const auto accel_state = m_accel_group.GetState(); const auto accel_state = m_accel_group.GetState();
const auto state = accel_state.value_or(Common::Vec3{}); const auto state = accel_state.value_or(Common::Vec3{});
// Bounding box size:
const double scale = GetScale();
QPainter p(this); QPainter p(this);
p.translate(width() / 2, height() / 2); p.setRenderHint(QPainter::TextAntialiasing, true);
DrawBoundingBox(p);
// Bounding box. TransformPainter(p);
p.setBrush(GetBBoxBrush());
p.setPen(GetBBoxPen());
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// UI axes are opposite that of Wii remote accelerometer. // UI axes are opposite that of Wii remote accelerometer.
p.scale(-1.0, -1.0); p.scale(-1.0, -1.0);
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
const auto rotation = WiimoteEmu::GetMatrixFromAcceleration(state); const auto rotation = WiimoteEmu::GetMatrixFromAcceleration(state);
// Draw sphere. // Draw sphere.
p.setPen(Qt::NoPen); p.setPen(GetCosmeticPen(QPen(GetRawInputColor(), 0.5)));
p.setBrush(GetRawInputColor());
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) { GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) {
const auto pt = rotation * point; const auto pt = rotation * point;
if (pt.y > 0) if (pt.y > 0)
p.drawEllipse(QPointF(pt.x, pt.z) * scale * SPHERE_SIZE, 0.5f, 0.5f); p.drawPoint(QPointF(pt.x, pt.z) * SPHERE_SIZE);
}); });
// Sphere outline. // Sphere outline.
p.setPen(GetRawInputColor()); p.setPen(QPen(GetRawInputColor(), 0));
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE); p.drawEllipse(QPointF{}, SPHERE_SIZE, SPHERE_SIZE);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
@ -758,27 +596,32 @@ void AccelerometerMappingIndicator::paintEvent(QPaintEvent*)
const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST}; const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST};
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE) if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
{ {
p.setBrush(GetAdjustedInputColor()); p.setPen(GetInputDotPen(GetAdjustedInputColor()));
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawPoint(QPointF(point.x, point.z));
} }
// Blue dot. // Blue dot.
const auto point2 = -point; const auto point2 = -point;
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE) if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
{ {
p.setBrush(Qt::blue); p.setPen(GetInputDotPen(GetCenterColor()));
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawPoint(QPointF(point2.x, point2.z));
} }
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.resetTransform();
p.translate(width() / 2, height() / 2);
// Red dot upright target. // Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2)); p.setPen(GetAdjustedInputColor());
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * GetContentsScale(), INPUT_DOT_RADIUS,
INPUT_DOT_RADIUS);
// Blue dot target. // Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2)); p.setPen(GetCenterColor());
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * GetContentsScale(), INPUT_DOT_RADIUS,
INPUT_DOT_RADIUS);
// Only draw g-force text if acceleration data is present. // Only draw g-force text if acceleration data is present.
if (!accel_state.has_value()) if (!accel_state.has_value())
@ -786,19 +629,14 @@ void AccelerometerMappingIndicator::paintEvent(QPaintEvent*)
// G-force text: // G-force text:
p.setPen(GetTextColor()); p.setPen(GetTextColor());
p.scale(-1.0, -1.0); p.drawText(QRect(0, 0, NORMAL_INDICATOR_WIDTH / 2 - 2, NORMAL_INDICATOR_HEIGHT / 2 - 1),
p.drawText(QRectF(-2, 0, scale, scale), Qt::AlignBottom | Qt::AlignRight, Qt::AlignBottom | Qt::AlignRight,
QString::fromStdString( QString::fromStdString(
// i18n: "g" is the symbol for "gravitational force equivalent" (g-force). // i18n: "g" is the symbol for "gravitational force equivalent" (g-force).
fmt::format("{:.2f} g", state.Length() / WiimoteEmu::GRAVITY_ACCELERATION))); fmt::format("{:.2f} g", state.Length() / WiimoteEmu::GRAVITY_ACCELERATION)));
} }
GyroMappingIndicator::GyroMappingIndicator(ControllerEmu::IMUGyroscope* group) void GyroMappingIndicator::Draw()
: MappingIndicator(group), m_gyro_group(*group), m_state(Common::Matrix33::Identity())
{
}
void GyroMappingIndicator::paintEvent(QPaintEvent*)
{ {
const auto gyro_state = m_gyro_group.GetState(); const auto gyro_state = m_gyro_group.GetState();
const auto raw_gyro_state = m_gyro_group.GetRawState(); const auto raw_gyro_state = m_gyro_group.GetRawState();
@ -825,20 +663,9 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
// Use an empty rotation matrix if gyroscope data is not present. // Use an empty rotation matrix if gyroscope data is not present.
const auto rotation = (gyro_state.has_value() ? m_state : Common::Matrix33{}); const auto rotation = (gyro_state.has_value() ? m_state : Common::Matrix33{});
// Bounding box size:
const double scale = GetScale();
QPainter p(this); QPainter p(this);
p.translate(width() / 2, height() / 2); DrawBoundingBox(p);
TransformPainter(p);
// Bounding box.
p.setBrush(GetBBoxBrush());
p.setPen(GetBBoxPen());
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
// Deadzone. // Deadzone.
if (const auto deadzone_value = m_gyro_group.GetDeadzone(); deadzone_value) if (const auto deadzone_value = m_gyro_group.GetDeadzone(); deadzone_value)
@ -847,10 +674,8 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
static constexpr auto DEADZONE_DRAW_BOTTOM = 1; static constexpr auto DEADZONE_DRAW_BOTTOM = 1;
p.setPen(GetDeadZonePen()); p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush()); p.setBrush(GetDeadZoneBrush(p));
p.scale(-1.0, 1.0); p.drawRect(QRectF{-1, DEADZONE_DRAW_BOTTOM, 2, -DEADZONE_DRAW_SIZE});
p.drawRect(-scale, DEADZONE_DRAW_BOTTOM * scale, scale * 2, -scale * DEADZONE_DRAW_SIZE);
p.scale(-1.0, 1.0);
if (gyro_state.has_value()) if (gyro_state.has_value())
{ {
@ -858,34 +683,33 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
std::max({std::abs(jitter.x), std::abs(jitter.y), std::abs(jitter.z)}); std::max({std::abs(jitter.x), std::abs(jitter.y), std::abs(jitter.z)});
const auto jitter_line_y = const auto jitter_line_y =
std::min(max_jitter / deadzone_value * DEADZONE_DRAW_SIZE - DEADZONE_DRAW_BOTTOM, 1.0); std::min(max_jitter / deadzone_value * DEADZONE_DRAW_SIZE - DEADZONE_DRAW_BOTTOM, 1.0);
p.setPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS)); p.setPen(GetCosmeticPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS)));
p.drawLine(-scale, jitter_line_y * -scale, scale, jitter_line_y * -scale); p.drawLine(-1.0, jitter_line_y * -1.0, 1.0, jitter_line_y * -1.0);
// Sphere background. // Sphere background.
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(GetBBoxBrush()); p.setBrush(GetBBoxBrush());
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE); p.drawEllipse(QPointF{}, SPHERE_SIZE, SPHERE_SIZE);
} }
} }
// Sphere dots. // Sphere dots.
p.setPen(Qt::NoPen); p.setPen(GetCosmeticPen(QPen(GetRawInputColor(), 0.5)));
p.setBrush(GetRawInputColor());
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) { GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) {
const auto pt = rotation * point; const auto pt = rotation * point;
if (pt.y > 0) if (pt.y > 0)
p.drawEllipse(QPointF(pt.x, pt.z) * scale * SPHERE_SIZE, 0.5f, 0.5f); p.drawPoint(QPointF(pt.x, pt.z) * SPHERE_SIZE);
}); });
// Sphere outline. // Sphere outline.
const auto outline_color = is_stable ? const auto outline_color =
(m_gyro_group.IsCalibrating() ? Qt::blue : GetRawInputColor()) : is_stable ? (m_gyro_group.IsCalibrating() ? GetCenterColor() : GetRawInputColor()) :
GetAdjustedInputColor(); GetAdjustedInputColor();
p.setPen(outline_color); p.setPen(QPen(outline_color, 0));
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE); p.drawEllipse(QPointF{}, SPHERE_SIZE, SPHERE_SIZE);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
@ -893,32 +717,35 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST}; const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST};
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE) if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
{ {
p.setBrush(GetAdjustedInputColor()); p.setPen(GetInputDotPen(GetAdjustedInputColor()));
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawPoint(QPointF(point.x, point.z));
} }
// Blue dot. // Blue dot.
const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0}; const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0};
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE) if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
{ {
p.setBrush(Qt::blue); p.setPen(GetInputDotPen(GetCenterColor()));
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawPoint(QPointF(point2.x, point2.z));
} }
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.resetTransform();
p.translate(width() / 2, height() / 2);
// Red dot upright target. // Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2)); p.setPen(GetAdjustedInputColor());
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * GetContentsScale(), INPUT_DOT_RADIUS,
INPUT_DOT_RADIUS);
// Blue dot target. // Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2)); p.setPen(GetCenterColor());
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
} }
void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point) void ReshapableInputIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
{ {
// Bounding box size: // Bounding box size:
const double scale = GetScale();
const auto center = m_calibration_widget->GetCenter(); const auto center = m_calibration_widget->GetCenter();
// Input shape. // Input shape.
@ -926,40 +753,38 @@ void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.drawPolygon(GetPolygonFromRadiusGetter( p.drawPolygon(GetPolygonFromRadiusGetter(
[this](double angle) { return m_calibration_widget->GetCalibrationRadiusAtAngle(angle); }, [this](double angle) { return m_calibration_widget->GetCalibrationRadiusAtAngle(angle); },
scale, center)); center));
// Center. // Center.
if (center.x || center.y) if (center.x || center.y)
{ {
p.setPen(Qt::NoPen); p.setPen(GetInputDotPen(GetCenterColor()));
p.setBrush(GetCenterColor()); p.drawPoint(QPointF{center.x, center.y});
p.drawEllipse(QPointF{center.x, center.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
} }
// Stick position. // Stick position.
p.setPen(Qt::NoPen); p.setPen(GetInputDotPen(GetAdjustedInputColor()));
p.setBrush(GetAdjustedInputColor()); p.drawPoint(QPointF{point.x, point.y});
p.drawEllipse(QPointF{point.x, point.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
} }
void MappingIndicator::UpdateCalibrationWidget(Common::DVec2 point) void ReshapableInputIndicator::UpdateCalibrationWidget(Common::DVec2 point)
{ {
if (m_calibration_widget) if (m_calibration_widget)
m_calibration_widget->Update(point); m_calibration_widget->Update(point);
} }
bool MappingIndicator::IsCalibrating() const bool ReshapableInputIndicator::IsCalibrating() const
{ {
return m_calibration_widget && m_calibration_widget->IsCalibrating(); return m_calibration_widget && m_calibration_widget->IsCalibrating();
} }
void MappingIndicator::SetCalibrationWidget(CalibrationWidget* widget) void ReshapableInputIndicator::SetCalibrationWidget(CalibrationWidget* widget)
{ {
m_calibration_widget = widget; m_calibration_widget = widget;
} }
CalibrationWidget::CalibrationWidget(ControllerEmu::ReshapableInput& input, CalibrationWidget::CalibrationWidget(ControllerEmu::ReshapableInput& input,
MappingIndicator& indicator) ReshapableInputIndicator& indicator)
: m_input(input), m_indicator(indicator), m_completion_action{} : m_input(input), m_indicator(indicator), m_completion_action{}
{ {
m_indicator.SetCalibrationWidget(this); m_indicator.SetCalibrationWidget(this);

View File

@ -18,6 +18,7 @@ class Control;
class ControlGroup; class ControlGroup;
class Cursor; class Cursor;
class Force; class Force;
class MixedTriggers;
} // namespace ControllerEmu } // namespace ControllerEmu
class QPainter; class QPainter;
@ -29,10 +30,6 @@ class CalibrationWidget;
class MappingIndicator : public QWidget class MappingIndicator : public QWidget
{ {
public: public:
explicit MappingIndicator(ControllerEmu::ControlGroup* group);
void SetCalibrationWidget(CalibrationWidget* widget);
QPen GetBBoxPen() const; QPen GetBBoxPen() const;
QBrush GetBBoxBrush() const; QBrush GetBBoxBrush() const;
QColor GetRawInputColor() const; QColor GetRawInputColor() const;
@ -41,66 +38,145 @@ public:
QColor GetAdjustedInputColor() const; QColor GetAdjustedInputColor() const;
QColor GetDeadZoneColor() const; QColor GetDeadZoneColor() const;
QPen GetDeadZonePen() const; QPen GetDeadZonePen() const;
QBrush GetDeadZoneBrush() const; QBrush GetDeadZoneBrush(QPainter&) const;
QColor GetTextColor() const; QColor GetTextColor() const;
QColor GetAltTextColor() const; QColor GetAltTextColor() const;
void AdjustGateColor(QColor*); void AdjustGateColor(QColor*);
protected: protected:
double GetScale() const; virtual void Draw() {}
WiimoteEmu::MotionState m_motion_state{};
private: private:
void DrawCursor(ControllerEmu::Cursor& cursor);
void DrawReshapableInput(ControllerEmu::ReshapableInput& stick);
void DrawMixedTriggers();
void DrawForce(ControllerEmu::Force&);
void DrawCalibration(QPainter& p, Common::DVec2 point);
void paintEvent(QPaintEvent*) override; void paintEvent(QPaintEvent*) override;
};
class SquareIndicator : public MappingIndicator
{
protected:
SquareIndicator();
qreal GetContentsScale() const;
void DrawBoundingBox(QPainter&);
void TransformPainter(QPainter&);
};
class ReshapableInputIndicator : public SquareIndicator
{
public:
void SetCalibrationWidget(CalibrationWidget* widget);
protected:
void DrawReshapableInput(ControllerEmu::ReshapableInput& group, QColor gate_color,
std::optional<ControllerEmu::ReshapableInput::ReshapeData> adj_coord);
virtual void DrawUnderGate(QPainter&) {}
bool IsCalibrating() const; bool IsCalibrating() const;
void DrawCalibration(QPainter& p, Common::DVec2 point);
void UpdateCalibrationWidget(Common::DVec2 point); void UpdateCalibrationWidget(Common::DVec2 point);
ControllerEmu::ControlGroup* const m_group; private:
CalibrationWidget* m_calibration_widget{}; CalibrationWidget* m_calibration_widget{};
}; };
class ShakeMappingIndicator : public MappingIndicator class AnalogStickIndicator : public ReshapableInputIndicator
{ {
public: public:
explicit ShakeMappingIndicator(ControllerEmu::Shake* group); explicit AnalogStickIndicator(ControllerEmu::ReshapableInput& stick) : m_group(stick) {}
void DrawShake();
void paintEvent(QPaintEvent*) override;
private: private:
std::deque<ControllerEmu::Shake::StateData> m_position_samples; void Draw() override;
int m_grid_line_position = 0;
ControllerEmu::Shake& m_shake_group; ControllerEmu::ReshapableInput& m_group;
}; };
class AccelerometerMappingIndicator : public MappingIndicator class TiltIndicator : public ReshapableInputIndicator
{ {
public: public:
explicit AccelerometerMappingIndicator(ControllerEmu::IMUAccelerometer* group); explicit TiltIndicator(ControllerEmu::Tilt& tilt) : m_group(tilt) {}
void paintEvent(QPaintEvent*) override;
private: private:
void Draw() override;
ControllerEmu::Tilt& m_group;
WiimoteEmu::MotionState m_motion_state{};
};
class CursorIndicator : public ReshapableInputIndicator
{
public:
explicit CursorIndicator(ControllerEmu::Cursor& cursor) : m_cursor_group(cursor) {}
private:
void Draw() override;
ControllerEmu::Cursor& m_cursor_group;
};
class MixedTriggersIndicator : public MappingIndicator
{
public:
explicit MixedTriggersIndicator(ControllerEmu::MixedTriggers& triggers);
private:
void Draw() override;
ControllerEmu::MixedTriggers& m_group;
};
class SwingIndicator : public ReshapableInputIndicator
{
public:
explicit SwingIndicator(ControllerEmu::Force& swing) : m_swing_group(swing) {}
private:
void Draw() override;
void DrawUnderGate(QPainter& p) override;
ControllerEmu::Force& m_swing_group;
WiimoteEmu::MotionState m_motion_state{};
};
class ShakeMappingIndicator : public SquareIndicator
{
public:
explicit ShakeMappingIndicator(ControllerEmu::Shake& shake) : m_shake_group(shake) {}
private:
void Draw() override;
ControllerEmu::Shake& m_shake_group;
WiimoteEmu::MotionState m_motion_state{};
std::deque<ControllerEmu::Shake::StateData> m_position_samples;
int m_grid_line_position = 0;
};
class AccelerometerMappingIndicator : public SquareIndicator
{
public:
explicit AccelerometerMappingIndicator(ControllerEmu::IMUAccelerometer& accel)
: m_accel_group(accel)
{
}
private:
void Draw() override;
ControllerEmu::IMUAccelerometer& m_accel_group; ControllerEmu::IMUAccelerometer& m_accel_group;
}; };
class GyroMappingIndicator : public MappingIndicator class GyroMappingIndicator : public SquareIndicator
{ {
public: public:
explicit GyroMappingIndicator(ControllerEmu::IMUGyroscope* group); explicit GyroMappingIndicator(ControllerEmu::IMUGyroscope& gyro) : m_gyro_group(gyro) {}
void paintEvent(QPaintEvent*) override;
private: private:
void Draw() override;
ControllerEmu::IMUGyroscope& m_gyro_group; ControllerEmu::IMUGyroscope& m_gyro_group;
Common::Matrix33 m_state; Common::Matrix33 m_state = Common::Matrix33::Identity();
Common::Vec3 m_previous_velocity = {}; Common::Vec3 m_previous_velocity = {};
u32 m_stable_steps = 0; u32 m_stable_steps = 0;
}; };
@ -108,7 +184,7 @@ private:
class CalibrationWidget : public QToolButton class CalibrationWidget : public QToolButton
{ {
public: public:
CalibrationWidget(ControllerEmu::ReshapableInput& input, MappingIndicator& indicator); CalibrationWidget(ControllerEmu::ReshapableInput& input, ReshapableInputIndicator& indicator);
void Update(Common::DVec2 point); void Update(Common::DVec2 point);
@ -123,7 +199,7 @@ private:
void SetupActions(); void SetupActions();
ControllerEmu::ReshapableInput& m_input; ControllerEmu::ReshapableInput& m_input;
MappingIndicator& m_indicator; ReshapableInputIndicator& m_indicator;
QAction* m_completion_action; QAction* m_completion_action;
ControllerEmu::ReshapableInput::CalibrationData m_calibration_data; ControllerEmu::ReshapableInput::CalibrationData m_calibration_data;
QTimer* m_informative_timer; QTimer* m_informative_timer;

View File

@ -19,6 +19,7 @@
#include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h" #include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h" #include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerEmu/StickGate.h" #include "InputCommon/ControllerEmu/StickGate.h"
@ -52,52 +53,66 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
group_box->setLayout(form_layout); group_box->setLayout(form_layout);
const bool need_indicator = group->type == ControllerEmu::GroupType::Cursor || MappingIndicator* indicator = nullptr;
group->type == ControllerEmu::GroupType::Stick ||
group->type == ControllerEmu::GroupType::Tilt ||
group->type == ControllerEmu::GroupType::MixedTriggers ||
group->type == ControllerEmu::GroupType::Force ||
group->type == ControllerEmu::GroupType::IMUAccelerometer ||
group->type == ControllerEmu::GroupType::IMUGyroscope ||
group->type == ControllerEmu::GroupType::Shake;
const bool need_calibration = group->type == ControllerEmu::GroupType::Cursor || switch (group->type)
group->type == ControllerEmu::GroupType::Stick ||
group->type == ControllerEmu::GroupType::Tilt ||
group->type == ControllerEmu::GroupType::Force;
if (need_indicator)
{ {
MappingIndicator* indicator; case ControllerEmu::GroupType::Shake:
indicator = new ShakeMappingIndicator(*static_cast<ControllerEmu::Shake*>(group));
break;
switch (group->type) case ControllerEmu::GroupType::MixedTriggers:
{ indicator = new MixedTriggersIndicator(*static_cast<ControllerEmu::MixedTriggers*>(group));
case ControllerEmu::GroupType::Shake: break;
indicator = new ShakeMappingIndicator(static_cast<ControllerEmu::Shake*>(group));
break;
case ControllerEmu::GroupType::IMUAccelerometer: case ControllerEmu::GroupType::Tilt:
indicator = indicator = new TiltIndicator(*static_cast<ControllerEmu::Tilt*>(group));
new AccelerometerMappingIndicator(static_cast<ControllerEmu::IMUAccelerometer*>(group)); break;
break;
case ControllerEmu::GroupType::IMUGyroscope: case ControllerEmu::GroupType::Cursor:
indicator = new GyroMappingIndicator(static_cast<ControllerEmu::IMUGyroscope*>(group)); indicator = new CursorIndicator(*static_cast<ControllerEmu::Cursor*>(group));
break; break;
default: case ControllerEmu::GroupType::Force:
indicator = new MappingIndicator(group); indicator = new SwingIndicator(*static_cast<ControllerEmu::Force*>(group));
break; break;
}
form_layout->addRow(indicator); case ControllerEmu::GroupType::IMUAccelerometer:
indicator =
new AccelerometerMappingIndicator(*static_cast<ControllerEmu::IMUAccelerometer*>(group));
break;
case ControllerEmu::GroupType::IMUGyroscope:
indicator = new GyroMappingIndicator(*static_cast<ControllerEmu::IMUGyroscope*>(group));
break;
case ControllerEmu::GroupType::Stick:
indicator = new AnalogStickIndicator(*static_cast<ControllerEmu::ReshapableInput*>(group));
break;
default:
break;
}
if (indicator)
{
const auto indicator_layout = new QBoxLayout(QBoxLayout::Direction::Down);
indicator_layout->addWidget(indicator);
indicator_layout->setAlignment(Qt::AlignCenter);
form_layout->addRow(indicator_layout);
connect(this, &MappingWidget::Update, indicator, QOverload<>::of(&MappingIndicator::update)); connect(this, &MappingWidget::Update, indicator, QOverload<>::of(&MappingIndicator::update));
const bool need_calibration = group->type == ControllerEmu::GroupType::Cursor ||
group->type == ControllerEmu::GroupType::Stick ||
group->type == ControllerEmu::GroupType::Tilt ||
group->type == ControllerEmu::GroupType::Force;
if (need_calibration) if (need_calibration)
{ {
const auto calibrate = const auto calibrate =
new CalibrationWidget(*static_cast<ControllerEmu::ReshapableInput*>(group), *indicator); new CalibrationWidget(*static_cast<ControllerEmu::ReshapableInput*>(group),
*static_cast<ReshapableInputIndicator*>(indicator));
form_layout->addRow(calibrate); form_layout->addRow(calibrate);
} }
@ -105,7 +120,7 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
for (auto& control : group->controls) for (auto& control : group->controls)
{ {
auto* button = new MappingButton(this, control->control_ref.get(), !need_indicator); auto* button = new MappingButton(this, control->control_ref.get(), !indicator);
button->setMinimumWidth(100); button->setMinimumWidth(100);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

View File

@ -71,7 +71,7 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
timer->start(1000 / INDICATOR_UPDATE_FREQ); timer->start(1000 / INDICATOR_UPDATE_FREQ);
GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();
} }
@ -245,7 +245,7 @@ void MappingWindow::OnLoadProfilePressed()
m_controller->LoadConfig(ini.GetOrCreateSection("Profile")); m_controller->LoadConfig(ini.GetOrCreateSection("Profile"));
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();
} }
@ -438,7 +438,7 @@ void MappingWindow::OnDefaultFieldsPressed()
m_controller->LoadDefaults(g_controller_interface); m_controller->LoadDefaults(g_controller_interface);
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();
emit Save(); emit Save();
} }
@ -455,7 +455,7 @@ void MappingWindow::OnClearFieldsPressed()
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();
emit Save(); emit Save();
} }

View File

@ -74,4 +74,9 @@ ControlState MixedTriggers::GetThreshold() const
return m_threshold_setting.GetValue() / 100; return m_threshold_setting.GetValue() / 100;
} }
size_t MixedTriggers::GetTriggerCount() const
{
return controls.size() / 2;
}
} // namespace ControllerEmu } // namespace ControllerEmu

View File

@ -22,6 +22,8 @@ public:
ControlState GetDeadzone() const; ControlState GetDeadzone() const;
ControlState GetThreshold() const; ControlState GetThreshold() const;
size_t GetTriggerCount() const;
private: private:
SettingValue<double> m_threshold_setting; SettingValue<double> m_threshold_setting;
SettingValue<double> m_deadzone_setting; SettingValue<double> m_deadzone_setting;

View File

@ -153,7 +153,7 @@ public:
// references and GetState(), by extension. This prevents a race condition // references and GetState(), by extension. This prevents a race condition
// which happens while handling a hotplug event because a control reference's State() // which happens while handling a hotplug event because a control reference's State()
// could be called before we have finished updating the reference. // could be called before we have finished updating the reference.
static std::unique_lock<std::recursive_mutex> GetStateLock(); [[nodiscard]] static std::unique_lock<std::recursive_mutex> GetStateLock();
std::vector<std::unique_ptr<ControlGroup>> groups; std::vector<std::unique_ptr<ControlGroup>> groups;

View File

@ -309,7 +309,6 @@ DeviceContainer::DetectInput(u32 wait_ms, const std::vector<std::string>& device
for (auto& device_state : device_states) for (auto& device_state : device_states)
{ {
device_state.device->UpdateInput();
for (auto& input_state : device_state.input_states) for (auto& input_state : device_state.input_states)
{ {
// We want an input that was initially 0.0 and currently 1.0. // We want an input that was initially 0.0 and currently 1.0.