mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
DolphinQt/InputCommon: Move some calibration logic to InputCommon and make the "Calibrate" button also map inputs.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,13 @@ class QPaintEvent;
|
||||
class QTimer;
|
||||
|
||||
class CalibrationWidget;
|
||||
class MappingWidget;
|
||||
|
||||
namespace ciface::MappingCommon
|
||||
{
|
||||
class ReshapableInputMapper;
|
||||
class CalibrationBuilder;
|
||||
} // namespace ciface::MappingCommon
|
||||
|
||||
class MappingIndicator : public QWidget
|
||||
{
|
||||
@@ -79,15 +86,16 @@ class ReshapableInputIndicator : public SquareIndicator
|
||||
public:
|
||||
void SetCalibrationWidget(CalibrationWidget* widget);
|
||||
|
||||
virtual QColor GetGateBrushColor() const = 0;
|
||||
|
||||
protected:
|
||||
void DrawReshapableInput(ControllerEmu::ReshapableInput& group, QColor gate_color,
|
||||
void DrawReshapableInput(ControllerEmu::ReshapableInput& group,
|
||||
std::optional<ControllerEmu::ReshapableInput::ReshapeData> adj_coord);
|
||||
|
||||
virtual void DrawUnderGate(QPainter&) {}
|
||||
|
||||
bool IsCalibrating() const;
|
||||
|
||||
void DrawCalibration(QPainter& p, Common::DVec2 point);
|
||||
void UpdateCalibrationWidget(Common::DVec2 point);
|
||||
|
||||
private:
|
||||
@@ -99,6 +107,8 @@ class AnalogStickIndicator : public ReshapableInputIndicator
|
||||
public:
|
||||
explicit AnalogStickIndicator(ControllerEmu::ReshapableInput& stick) : m_group(stick) {}
|
||||
|
||||
QColor GetGateBrushColor() const final;
|
||||
|
||||
private:
|
||||
void Draw() override;
|
||||
|
||||
@@ -110,6 +120,8 @@ class TiltIndicator : public ReshapableInputIndicator
|
||||
public:
|
||||
explicit TiltIndicator(ControllerEmu::Tilt& tilt) : m_group(tilt) {}
|
||||
|
||||
QColor GetGateBrushColor() const final;
|
||||
|
||||
private:
|
||||
void Draw() override;
|
||||
void Update(float elapsed_seconds) override;
|
||||
@@ -123,6 +135,8 @@ class CursorIndicator : public ReshapableInputIndicator
|
||||
public:
|
||||
explicit CursorIndicator(ControllerEmu::Cursor& cursor) : m_cursor_group(cursor) {}
|
||||
|
||||
QColor GetGateBrushColor() const final;
|
||||
|
||||
private:
|
||||
void Draw() override;
|
||||
|
||||
@@ -145,6 +159,8 @@ class SwingIndicator : public ReshapableInputIndicator
|
||||
public:
|
||||
explicit SwingIndicator(ControllerEmu::Force& swing) : m_swing_group(swing) {}
|
||||
|
||||
QColor GetGateBrushColor() const final;
|
||||
|
||||
private:
|
||||
void Draw() override;
|
||||
void Update(float elapsed_seconds) override;
|
||||
@@ -219,28 +235,46 @@ private:
|
||||
|
||||
ControllerEmu::IRPassthrough& m_ir_group;
|
||||
};
|
||||
|
||||
class CalibrationWidget : public QToolButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CalibrationWidget(ControllerEmu::ReshapableInput& input, ReshapableInputIndicator& indicator);
|
||||
CalibrationWidget(MappingWidget& mapping_widget, ControllerEmu::ReshapableInput& input,
|
||||
ReshapableInputIndicator& indicator);
|
||||
~CalibrationWidget() override;
|
||||
|
||||
void Update(Common::DVec2 point);
|
||||
|
||||
double GetCalibrationRadiusAtAngle(double angle) const;
|
||||
void Draw(QPainter& p, Common::DVec2 point);
|
||||
|
||||
Common::DVec2 GetCenter() const;
|
||||
bool IsActive() const;
|
||||
|
||||
bool IsCalibrating() const;
|
||||
signals:
|
||||
void CalibrationIsSensible();
|
||||
|
||||
private:
|
||||
void StartCalibration();
|
||||
void SetupActions();
|
||||
void DrawInProgressMapping(QPainter& p);
|
||||
void DrawInProgressCalibration(QPainter& p, Common::DVec2 point);
|
||||
|
||||
bool IsMapping() const;
|
||||
bool IsCalibrating() const;
|
||||
|
||||
void StartMappingAndCalibration();
|
||||
void StartCalibration(std::optional<Common::DVec2> center = Common::DVec2{});
|
||||
|
||||
void ResetActions();
|
||||
void DeleteAllActions();
|
||||
|
||||
MappingWidget& m_mapping_widget;
|
||||
ControllerEmu::ReshapableInput& m_input;
|
||||
ReshapableInputIndicator& m_indicator;
|
||||
QAction* m_completion_action;
|
||||
ControllerEmu::ReshapableInput::CalibrationData m_calibration_data;
|
||||
QTimer* m_informative_timer;
|
||||
std::optional<Common::DVec2> m_new_center;
|
||||
Common::DVec2 m_prev_point;
|
||||
|
||||
std::unique_ptr<ciface::MappingCommon::ReshapableInputMapper> m_mapper;
|
||||
std::unique_ptr<ciface::MappingCommon::CalibrationBuilder> m_calibrator;
|
||||
|
||||
double GetAnimationElapsedSeconds() const;
|
||||
void RestartAnimation();
|
||||
|
||||
Clock::time_point m_animation_start_time{};
|
||||
};
|
||||
|
||||
@@ -152,7 +152,7 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
|
||||
if (need_calibration)
|
||||
{
|
||||
const auto calibrate =
|
||||
new CalibrationWidget(*static_cast<ControllerEmu::ReshapableInput*>(group),
|
||||
new CalibrationWidget(*this, *static_cast<ControllerEmu::ReshapableInput*>(group),
|
||||
*static_cast<ReshapableInputIndicator*>(indicator));
|
||||
|
||||
form_layout->addRow(calibrate);
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
<ClInclude Include="Config\ConfigControls\ConfigControl.h" />
|
||||
<ClInclude Include="Config\GameConfigEdit.h" />
|
||||
<ClInclude Include="Config\Mapping\MappingCommon.h" />
|
||||
<ClInclude Include="Config\Mapping\MappingIndicator.h" />
|
||||
<QtMoc Include="Config\Mapping\MappingIndicator.h" />
|
||||
<ClInclude Include="Config\Mapping\MappingNumeric.h" />
|
||||
<ClInclude Include="Config\NewPatchDialog.h" />
|
||||
<QtMoc Include="Config\PatchesWidget.h" />
|
||||
|
||||
@@ -5,14 +5,21 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerEmu/StickGate.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/ControllerInterface/CoreDevice.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
namespace ciface::MappingCommon
|
||||
{
|
||||
@@ -163,4 +170,127 @@ bool ContainsCompleteDetection(const Core::InputDetector::Results& results)
|
||||
});
|
||||
}
|
||||
|
||||
ReshapableInputMapper::ReshapableInputMapper(const Core::DeviceContainer& container,
|
||||
std::span<const std::string> device_strings)
|
||||
{
|
||||
m_input_detector.Start(container, device_strings);
|
||||
}
|
||||
|
||||
bool ReshapableInputMapper::Update()
|
||||
{
|
||||
const auto prev_size = m_input_detector.GetResults().size();
|
||||
|
||||
constexpr auto wait_time = std::chrono::seconds{4};
|
||||
m_input_detector.Update(wait_time, wait_time, wait_time * REQUIRED_INPUT_COUNT);
|
||||
|
||||
return m_input_detector.GetResults().size() != prev_size;
|
||||
}
|
||||
|
||||
float ReshapableInputMapper::GetCurrentAngle() const
|
||||
{
|
||||
constexpr auto quarter_circle = float(MathUtil::TAU) * 0.25f;
|
||||
return quarter_circle - (float(m_input_detector.GetResults().size()) * quarter_circle);
|
||||
}
|
||||
|
||||
bool ReshapableInputMapper::IsComplete() const
|
||||
{
|
||||
return m_input_detector.GetResults().size() >= REQUIRED_INPUT_COUNT ||
|
||||
m_input_detector.IsComplete();
|
||||
}
|
||||
|
||||
bool ReshapableInputMapper::IsCalibrationNeeded() const
|
||||
{
|
||||
return std::ranges::any_of(m_input_detector.GetResults() | std::views::take(REQUIRED_INPUT_COUNT),
|
||||
&ciface::Core::InputDetector::Detection::IsAnalogPress);
|
||||
}
|
||||
|
||||
bool ReshapableInputMapper::ApplyResults(ControllerEmu::EmulatedController* controller,
|
||||
ControllerEmu::ReshapableInput* stick)
|
||||
{
|
||||
auto const detections = m_input_detector.TakeResults();
|
||||
|
||||
if (detections.size() < REQUIRED_INPUT_COUNT)
|
||||
return false;
|
||||
|
||||
// Transpose URDL to UDLR.
|
||||
const std::array results{detections[0], detections[2], detections[3], detections[1]};
|
||||
|
||||
const auto default_device = controller->GetDefaultDevice();
|
||||
|
||||
for (std::size_t i = 0; i != results.size(); ++i)
|
||||
{
|
||||
ciface::Core::DeviceQualifier device_qualifier;
|
||||
device_qualifier.FromDevice(results[i].device.get());
|
||||
|
||||
stick->controls[i]->control_ref->SetExpression(ciface::MappingCommon::GetExpressionForControl(
|
||||
results[i].input->GetName(), device_qualifier, default_device,
|
||||
ciface::MappingCommon::Quote::On));
|
||||
|
||||
controller->UpdateSingleControlReference(g_controller_interface,
|
||||
stick->controls[i]->control_ref.get());
|
||||
}
|
||||
|
||||
controller->GetConfig()->GenerateControllerTextures();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CalibrationBuilder::CalibrationBuilder(std::optional<Common::DVec2> center)
|
||||
: m_calibration_data(ControllerEmu::ReshapableInput::CALIBRATION_SAMPLE_COUNT, 0.0),
|
||||
m_center{center}
|
||||
{
|
||||
}
|
||||
|
||||
void CalibrationBuilder::Update(Common::DVec2 point)
|
||||
{
|
||||
if (!m_center.has_value())
|
||||
m_center = point;
|
||||
|
||||
const auto new_point = point - *m_center;
|
||||
ControllerEmu::ReshapableInput::UpdateCalibrationData(m_calibration_data, m_prev_point,
|
||||
new_point);
|
||||
m_prev_point = new_point;
|
||||
}
|
||||
|
||||
bool CalibrationBuilder::IsCalibrationDataSensible() const
|
||||
{
|
||||
// Even the GC controller's small range would pass this test.
|
||||
constexpr double REASONABLE_AVERAGE_RADIUS = 0.6;
|
||||
|
||||
// Test that the average input radius is not below a threshold.
|
||||
// This will make sure the user has actually moved their stick from neutral.
|
||||
|
||||
MathUtil::RunningVariance<ControlState> stats;
|
||||
|
||||
for (const auto x : m_calibration_data)
|
||||
stats.Push(x);
|
||||
|
||||
if (stats.Mean() < REASONABLE_AVERAGE_RADIUS)
|
||||
return false;
|
||||
|
||||
// Test that the standard deviation is below a threshold.
|
||||
// This will make sure the user has not just filled in one side of their input.
|
||||
|
||||
// Approx. deviation of a square input gate, anything much more than that would be unusual.
|
||||
constexpr double REASONABLE_DEVIATION = 0.14;
|
||||
|
||||
return stats.StandardDeviation() < REASONABLE_DEVIATION;
|
||||
}
|
||||
|
||||
ControlState CalibrationBuilder::GetCalibrationRadiusAtAngle(double angle) const
|
||||
{
|
||||
return ControllerEmu::ReshapableInput::GetCalibrationDataRadiusAtAngle(m_calibration_data, angle);
|
||||
}
|
||||
|
||||
void CalibrationBuilder::ApplyResults(ControllerEmu::ReshapableInput* stick)
|
||||
{
|
||||
stick->SetCenter(GetCenter());
|
||||
stick->SetCalibrationData(std::move(m_calibration_data));
|
||||
}
|
||||
|
||||
Common::DVec2 CalibrationBuilder::GetCenter() const
|
||||
{
|
||||
return m_center.value_or(Common::DVec2{});
|
||||
}
|
||||
|
||||
} // namespace ciface::MappingCommon
|
||||
|
||||
@@ -5,8 +5,15 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Common/Matrix.h"
|
||||
#include "InputCommon/ControllerEmu/StickGate.h"
|
||||
#include "InputCommon/ControllerInterface/CoreDevice.h"
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class EmulatedController;
|
||||
} // namespace ControllerEmu
|
||||
|
||||
namespace ciface::MappingCommon
|
||||
{
|
||||
enum class Quote
|
||||
@@ -27,4 +34,73 @@ void RemoveSpuriousTriggerCombinations(Core::InputDetector::Results*);
|
||||
void RemoveDetectionsAfterTimePoint(Core::InputDetector::Results*, Clock::time_point after);
|
||||
bool ContainsCompleteDetection(const Core::InputDetector::Results&);
|
||||
|
||||
// class for detecting four directional input mappings in sequence.
|
||||
class ReshapableInputMapper
|
||||
{
|
||||
public:
|
||||
// Four cardinal directions.
|
||||
static constexpr std::size_t REQUIRED_INPUT_COUNT = 4;
|
||||
|
||||
// Caller should hold the "StateLock".
|
||||
ReshapableInputMapper(const Core::DeviceContainer& container,
|
||||
std::span<const std::string> device_strings);
|
||||
|
||||
// Reads inputs and updates internal state.
|
||||
// Returns true if an input was detected in this call.
|
||||
// (useful for UI animation)
|
||||
// Caller should hold the "StateLock".
|
||||
bool Update();
|
||||
|
||||
// A counter-clockwise angle in radians for the currently desired input direction.
|
||||
// Used for a graphical indicator in the UI.
|
||||
// 0 == East
|
||||
float GetCurrentAngle() const;
|
||||
|
||||
// True if all four directions have been detected or the timer expired.
|
||||
bool IsComplete() const;
|
||||
|
||||
// Returns true if "analog" inputs were detected and calibration should be performed.
|
||||
// Must use *before* ApplyResults.
|
||||
bool IsCalibrationNeeded() const;
|
||||
|
||||
// Use when IsComplete returns true.
|
||||
// Updates the mappings on the provided ReshapableInput.
|
||||
// Caller should hold the "StateLock".
|
||||
bool ApplyResults(ControllerEmu::EmulatedController*, ControllerEmu::ReshapableInput* stick);
|
||||
|
||||
private:
|
||||
Core::InputDetector m_input_detector;
|
||||
};
|
||||
|
||||
class CalibrationBuilder
|
||||
{
|
||||
public:
|
||||
// Provide nullopt if you want to calibrate the center on first Update.
|
||||
explicit CalibrationBuilder(std::optional<Common::DVec2> center = Common::DVec2{});
|
||||
|
||||
// Updates the calibration data using the provided point and the previous point.
|
||||
void Update(Common::DVec2 point);
|
||||
|
||||
// Returns true when the calibration data seems to be reasonably filled in.
|
||||
// Used to update the UI to encourage the user to click the "Finish" button.
|
||||
bool IsCalibrationDataSensible() const;
|
||||
|
||||
// Grabs the calibration value at the provided angle.
|
||||
// Used to render the calibration in the UI while it's in progress.
|
||||
ControlState GetCalibrationRadiusAtAngle(double angle) const;
|
||||
|
||||
// Sets the calibration data of the provided ReshapableInput.
|
||||
// Caller should hold the "StateLock".
|
||||
void ApplyResults(ControllerEmu::ReshapableInput* stick);
|
||||
|
||||
Common::DVec2 GetCenter() const;
|
||||
|
||||
private:
|
||||
ControllerEmu::ReshapableInput::CalibrationData m_calibration_data;
|
||||
|
||||
std::optional<Common::DVec2> m_center = std::nullopt;
|
||||
|
||||
Common::DVec2 m_prev_point{};
|
||||
};
|
||||
|
||||
} // namespace ciface::MappingCommon
|
||||
|
||||
Reference in New Issue
Block a user