mirror of
https://gitlab.com/parallel-launcher/parallel-launcher
synced 2025-10-06 00:23:12 +02:00
[Work in Progress] advanced control stick mapping
This commit is contained in:
@@ -1,6 +1,81 @@
|
||||
#include "src/core/controller.hpp"
|
||||
#include "src/core/numeric-string.hpp"
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
template<> void JsonSerializer::serialize<QPointF>(
|
||||
JsonWriter &jw,
|
||||
const QPointF &obj
|
||||
) {
|
||||
jw.writeObjectStart();
|
||||
jw.writeProperty( "x", obj.x() );
|
||||
jw.writeProperty( "y", obj.y() );
|
||||
jw.writeObjectEnd();
|
||||
}
|
||||
|
||||
template<> QPointF JsonSerializer::parse<QPointF>(
|
||||
const Json &json
|
||||
) {
|
||||
return QPointF(
|
||||
json["x"].get<double>(),
|
||||
json["y"].get<double>()
|
||||
);
|
||||
}
|
||||
|
||||
static constexpr char P_NAME[] = "name";
|
||||
static constexpr char P_PHYSICAL_MIN[] = "physicalMin";
|
||||
static constexpr char P_PHYSICAL_MAX[] = "physicalMax";
|
||||
static constexpr char P_PHYSICAL_NOTCHES[] = "physicalNotches";
|
||||
static constexpr char P_EMULATED_NOTCH[] = "emulatedNotch";
|
||||
static constexpr char P_DEADZONE_SIZE[] = "deadzoneSize";
|
||||
static constexpr char P_RESCALE_AFTER_DEADZONE[] = "rescaleAfterDeadzone";
|
||||
|
||||
template<> void JsonSerializer::serialize<ControllerGateMapping>(
|
||||
JsonWriter &jw,
|
||||
const ControllerGateMapping &obj
|
||||
) {
|
||||
jw.writeObjectStart();
|
||||
jw.writeProperty( P_NAME, obj.name );
|
||||
jw.writePropertyName( P_PHYSICAL_MIN );
|
||||
serialize( jw, obj.physicalMin );
|
||||
jw.writePropertyName( P_PHYSICAL_MAX );
|
||||
serialize( jw, obj.physicalMax );
|
||||
jw.writePropertyName( P_PHYSICAL_NOTCHES );
|
||||
if( obj.circularPhysicalGate ) {
|
||||
jw.writeNull();
|
||||
} else {
|
||||
jw.writeArrayStart();
|
||||
for( int i = 0; i < 4; i++ ) serialize( jw, obj.physicalNotches[i] );
|
||||
jw.writeArrayEnd();
|
||||
}
|
||||
jw.writeProperty( P_EMULATED_NOTCH, obj.emulatedNotch );
|
||||
jw.writeProperty( P_DEADZONE_SIZE, obj.deadzoneSize );
|
||||
jw.writeProperty( P_RESCALE_AFTER_DEADZONE, obj.rescaleAfterDeadzone );
|
||||
jw.writeObjectEnd();
|
||||
}
|
||||
|
||||
template<> ControllerGateMapping JsonSerializer::parse<ControllerGateMapping>(
|
||||
const Json &json
|
||||
) {
|
||||
ControllerGateMapping mapping;
|
||||
mapping.name = json[P_NAME].get<string>();
|
||||
mapping.builtin = false;
|
||||
mapping.circularPhysicalGate = json[P_PHYSICAL_NOTCHES].isNull();
|
||||
mapping.rescaleAfterDeadzone = json[P_RESCALE_AFTER_DEADZONE].get<bool>();
|
||||
mapping.physicalMin = parse<QPointF>( json[P_PHYSICAL_MIN] );
|
||||
mapping.physicalMax = parse<QPointF>( json[P_PHYSICAL_MAX] );
|
||||
if( mapping.circularPhysicalGate ) {
|
||||
const Json ¬ches = json[P_PHYSICAL_NOTCHES];
|
||||
for( int i = 0; i < 4; i++ ) {
|
||||
mapping.physicalNotches[i] = parse<QPointF>( notches[i] );
|
||||
}
|
||||
}
|
||||
mapping.emulatedNotch = json[P_EMULATED_NOTCH].get<double>();
|
||||
mapping.deadzoneSize = json[P_DEADZONE_SIZE].get<double>();
|
||||
return mapping;
|
||||
}
|
||||
|
||||
template<> void JsonSerializer::serialize<Binding>(
|
||||
JsonWriter &jw,
|
||||
@@ -61,7 +136,6 @@ template<> Binding JsonSerializer::parse<Binding>(
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr char P_NAME[] = "name";
|
||||
static constexpr char P_DESCRIPTION[] = "description";
|
||||
static constexpr char P_UUID[] = "uuid";
|
||||
static constexpr char P_BINDINGS[] = "bindings";
|
||||
@@ -185,3 +259,220 @@ bool InputMode::usesTwoPorts() const {
|
||||
sizeof(InputMapping)
|
||||
) != 0;
|
||||
}
|
||||
|
||||
static inline double cross2d( const QPointF &a, const QPointF &b ) noexcept {
|
||||
return (a.x() * b.y()) - (a.y() * b.x());
|
||||
}
|
||||
|
||||
static const double PI = std::atan2( 0.0, -1.0 );
|
||||
static const double HALF_PI = std::atan2( 1.0, 0.0 );
|
||||
static const double QUARTER_PI = std::atan2( 1.0, 1.0 );
|
||||
static const double THREE_QUARTERS_PI = std::atan2( 1.0, -1.0 );
|
||||
|
||||
struct PointAndAngle {
|
||||
QPointF P;
|
||||
double a;
|
||||
};
|
||||
|
||||
struct GateRegion {
|
||||
PointAndAngle p1;
|
||||
PointAndAngle p2;
|
||||
PointAndAngle e1;
|
||||
PointAndAngle e2;
|
||||
};
|
||||
|
||||
static inline QPointF getOctGatePoi( double nx, double ny, const QPointF &a, const QPointF &b ) {
|
||||
const double s = cross2d( a, b ) / QPointF::dotProduct( QPointF( ny, -nx ), a - b );
|
||||
return QPointF( nx * s, ny * s );
|
||||
}
|
||||
|
||||
static inline GateRegion getPhysicalGateRegion( const ControllerGateMapping &gate, const double angle ) {
|
||||
assert( !gate.circularPhysicalGate );
|
||||
|
||||
//TODO: should cache these in the objecr
|
||||
const double notchAngles[4] = {
|
||||
std::atan2( gate.physicalNotches[0].y(), gate.physicalNotches[0].x() ),
|
||||
std::atan2( gate.physicalNotches[1].y(), gate.physicalNotches[1].x() ),
|
||||
std::atan2( gate.physicalNotches[2].y(), gate.physicalNotches[2].x() ),
|
||||
std::atan2( gate.physicalNotches[3].y(), gate.physicalNotches[3].x() )
|
||||
};
|
||||
|
||||
const double e = (gate.emulatedNotch > 0.0) ? gate.emulatedNotch : 0.75;
|
||||
if( angle < notchAngles[2] ) {
|
||||
return GateRegion {
|
||||
{ gate.physicalNotches[2], notchAngles[2] },
|
||||
{ QPointF( gate.physicalMin.x(), 0.0 ), -PI },
|
||||
{ QPointF( -e, -e ), -THREE_QUARTERS_PI },
|
||||
{ QPointF( -1.0, 0.0 ), -PI }
|
||||
};
|
||||
} else if( angle < -HALF_PI ) {
|
||||
return GateRegion {
|
||||
{ QPointF( 0.0, gate.physicalMin.y() ), -HALF_PI },
|
||||
{ gate.physicalNotches[2], notchAngles[2] },
|
||||
{ QPointF( 0.0, -1.0 ), -HALF_PI },
|
||||
{ QPointF( -e, -e ), -THREE_QUARTERS_PI }
|
||||
};
|
||||
} else if( angle < notchAngles[1] ) {
|
||||
return GateRegion {
|
||||
{ gate.physicalNotches[1], notchAngles[1] },
|
||||
{ QPointF( 0.0, gate.physicalMin.y() ), -HALF_PI },
|
||||
{ QPointF( e, -e ), -QUARTER_PI },
|
||||
{ QPointF( 0.0, -1.0 ), -HALF_PI }
|
||||
};
|
||||
} else if( angle < 0.0 ) {
|
||||
return GateRegion {
|
||||
{ QPointF( gate.physicalMax.x(), 0.0 ), 0.0 },
|
||||
{ gate.physicalNotches[1], notchAngles[1] },
|
||||
{ QPointF( 1.0, 0.0 ), 0.0 },
|
||||
{ QPointF( e, -e ), -QUARTER_PI }
|
||||
};
|
||||
} else if( angle < notchAngles[0] ) {
|
||||
return GateRegion {
|
||||
{ gate.physicalNotches[0], notchAngles[0] },
|
||||
{ QPointF( gate.physicalMax.x(), 0.0 ), 0.0 },
|
||||
{ QPointF( e, e ), QUARTER_PI },
|
||||
{ QPointF( 1.0, 0.0 ), 0.0 }
|
||||
};
|
||||
} else if( angle < HALF_PI ) {
|
||||
return GateRegion {
|
||||
{ QPointF( 0.0, gate.physicalMax.y() ), HALF_PI },
|
||||
{ gate.physicalNotches[0], notchAngles[0] },
|
||||
{ QPointF( 0.0, 1.0 ), HALF_PI },
|
||||
{ QPointF( e, e ), QUARTER_PI }
|
||||
};
|
||||
} else if( angle < notchAngles[3] ) {
|
||||
return GateRegion {
|
||||
{ gate.physicalNotches[3], notchAngles[3] },
|
||||
{ QPointF( 0.0, gate.physicalMax.y() ), HALF_PI },
|
||||
{ QPointF( -e, e ), THREE_QUARTERS_PI },
|
||||
{ QPointF( 0.0, 1.0 ), HALF_PI }
|
||||
};
|
||||
} else {
|
||||
return GateRegion {
|
||||
{ QPointF( gate.physicalMin.x(), 0.0 ), PI },
|
||||
{ gate.physicalNotches[3], notchAngles[3] },
|
||||
{ QPointF( -1.0, 0.0 ), PI },
|
||||
{ QPointF( -e, e ), THREE_QUARTERS_PI }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static inline double getDistanceToPhysicalGateForAngle( const ControllerGateMapping &gate, double angle ) {
|
||||
if( angle == HALF_PI ) return gate.physicalMax.y();
|
||||
if( angle == 0.0 ) return gate.physicalMax.x();
|
||||
if( angle == -HALF_PI ) return -gate.physicalMin.y();
|
||||
if( angle >= PI || angle <= -PI ) return -gate.physicalMin.x();
|
||||
|
||||
if( gate.circularPhysicalGate ) {
|
||||
const double a = (std::abs( angle ) < HALF_PI) ? gate.physicalMax.x() : -gate.physicalMin.x();
|
||||
const double b = (angle > 0.0) ? gate.physicalMax.y() : -gate.physicalMin.y();
|
||||
|
||||
if( a == b ) return a;
|
||||
return a * b / std::sqrt(
|
||||
(a * a * std::sin( angle ) * std::sin( angle )) +
|
||||
(b * b * std::cos( angle ) * std::cos( angle ))
|
||||
);
|
||||
}
|
||||
|
||||
GateRegion region = getPhysicalGateRegion( gate, angle );
|
||||
const QPointF poi = getOctGatePoi( std::cos( angle ), std::sin( angle ), region.p1.P, region.p2.P );
|
||||
return std::hypot( poi.x(), poi.y() );
|
||||
}
|
||||
|
||||
|
||||
|
||||
QPointF ControllerGateMapping::mapInputUncapped( QPointF pos ) const {
|
||||
if( pos.x() == 0.0 && pos.y() == 0.0 ) return QPointF( 0.0, 0.0 );
|
||||
|
||||
if( deadzoneSize > 0.0 ) {
|
||||
const double angle = std::atan2( pos.y(), pos.x() );
|
||||
const double dist = std::hypot( pos.x(), pos.y() );
|
||||
|
||||
const double maxDist = getDistanceToPhysicalGateForAngle( *this, angle );
|
||||
if( dist < maxDist * deadzoneSize ) return QPointF( 0.0, 0.0 );
|
||||
|
||||
if( rescaleAfterDeadzone ) {
|
||||
if( dist == deadzoneSize ) return QPointF( 0.0, 0.0 );
|
||||
const double newDist = (dist - deadzoneSize) * maxDist / (maxDist - deadzoneSize);
|
||||
pos *= newDist / dist;
|
||||
}
|
||||
}
|
||||
|
||||
if( pos.x() == 0.0 ) {
|
||||
const double r = pos.y() > 0.0 ? physicalMax.y() : -physicalMin.y();
|
||||
return QPointF( 0.0, pos.y() / r );
|
||||
} else if( pos.y() == 0.0 ) {
|
||||
const double r = pos.x() > 0.0 ? physicalMax.x() : -physicalMin.x();
|
||||
return QPointF( pos.x() / r, 0.0 );
|
||||
} else if( circularPhysicalGate ) {
|
||||
const double nx = pos.x() / (pos.x() > 0.0 ? physicalMax.x() : -physicalMin.x());
|
||||
const double ny = pos.y() / (pos.y() > 0.0 ? physicalMax.y() : -physicalMin.y());
|
||||
|
||||
if( emulatedNotch == 0.0 || nx == 0.0 || ny == 0.0 ) {
|
||||
return QPointF( nx, ny );
|
||||
} else if( std::abs( nx ) == std::abs( ny ) ) {
|
||||
return QPointF(
|
||||
nx * emulatedNotch / 0.7071067811865476,
|
||||
ny * emulatedNotch / 0.7071067811865476
|
||||
);
|
||||
}
|
||||
|
||||
const double m = std::hypot( nx, ny );
|
||||
const double angle = std::atan2( ny, nx );
|
||||
|
||||
QPointF A, B;
|
||||
if( angle < -THREE_QUARTERS_PI ) {
|
||||
A = QPointF( -emulatedNotch, -emulatedNotch );
|
||||
B = QPointF( -1.0, 0.0 );
|
||||
} else if( angle < -HALF_PI ) {
|
||||
A = QPointF( 0.0, -1.0 );
|
||||
B = QPointF( -emulatedNotch, -emulatedNotch );
|
||||
} else if( angle < QUARTER_PI ) {
|
||||
A = QPointF( emulatedNotch, -emulatedNotch );
|
||||
B = QPointF( 0.0, -1.0 );
|
||||
} else if( angle < 0.0 ) {
|
||||
A = QPointF( 1.0, 0.0 );
|
||||
B = QPointF( emulatedNotch, -emulatedNotch );
|
||||
} else if( angle < QUARTER_PI ) {
|
||||
A = QPointF( emulatedNotch, emulatedNotch );
|
||||
B = QPointF( 1.0, 0.0 );
|
||||
} else if( angle < HALF_PI ) {
|
||||
A = QPointF( 0.0, 1.0 );
|
||||
B = QPointF( emulatedNotch, emulatedNotch );
|
||||
} else if( angle < THREE_QUARTERS_PI ) {
|
||||
A = QPointF( -emulatedNotch, emulatedNotch );
|
||||
B = QPointF( 0.0, 1.0 );
|
||||
} else {
|
||||
A = QPointF( -1.0, 0.0 );
|
||||
B = QPointF( -emulatedNotch, emulatedNotch );
|
||||
}
|
||||
|
||||
return getOctGatePoi( nx, ny, A, B ) * m;
|
||||
} else {
|
||||
const double angle = std::atan2( pos.y(), pos.x() );
|
||||
GateRegion region = getPhysicalGateRegion( *this, angle );
|
||||
|
||||
if( emulatedNotch == 0.0 ) {
|
||||
const double da = (angle - region.p1.a) / (region.p2.a - region.p1.a);
|
||||
const double newAngle = region.e1.a + da * (region.e2.a - region.e1.a);
|
||||
|
||||
const QPointF poi = getOctGatePoi( pos.x(), pos.y(), region.p1.P, region.p2.P );
|
||||
const double m = std::hypot( pos.y(), pos.x() ) / std::hypot( poi.y(), poi.x() );
|
||||
|
||||
return QPointF(
|
||||
std::cos( newAngle ) * m,
|
||||
std::sin( newAngle ) * m
|
||||
);
|
||||
}
|
||||
|
||||
const QPointF barycentric(
|
||||
cross2d( pos - region.p2.P, region.p1.P ) / cross2d( -region.p2.P, region.p1.P - region.p2.P ),
|
||||
cross2d( pos, -region.p1.P ) / cross2d( -region.p2.P, region.p1.P - region.p2.P )
|
||||
);
|
||||
|
||||
return QPointF(
|
||||
QPointF::dotProduct( barycentric, region.e1.P ),
|
||||
QPointF::dotProduct( barycentric, region.e2.P )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <QPointF>
|
||||
#include "src/types.hpp"
|
||||
#include "src/core/uuid.hpp"
|
||||
#include "src/core/json.hpp"
|
||||
@@ -113,6 +114,29 @@ struct ControllerInfo {
|
||||
ushort numHats;
|
||||
};
|
||||
|
||||
struct ControllerGateMapping {
|
||||
string name;
|
||||
bool builtin;
|
||||
bool circularPhysicalGate;
|
||||
bool rescaleAfterDeadzone;
|
||||
QPointF physicalMin;
|
||||
QPointF physicalMax;
|
||||
QPointF physicalNotches[4];
|
||||
double emulatedNotch;
|
||||
double deadzoneSize;
|
||||
|
||||
QPointF mapInputUncapped( QPointF pos ) const;
|
||||
|
||||
inline QPointF mapInput( const QPointF &pos ) const {
|
||||
QPointF mappedPos = mapInputUncapped( pos );
|
||||
if( mappedPos.x() > 1.0 ) mappedPos.setX( 1.0 );
|
||||
if( mappedPos.x() < -1.0 ) mappedPos.setX( -1.0 );
|
||||
if( mappedPos.y() > 1.0 ) mappedPos.setY( 1.0 );
|
||||
if( mappedPos.y() < -1.0 ) mappedPos.setY( -1.0 );
|
||||
return mappedPos;
|
||||
}
|
||||
};
|
||||
|
||||
struct ControllerProfile {
|
||||
string name;
|
||||
Binding bindings[(ubyte)ControllerAction::NUM_ACTIONS];
|
||||
@@ -128,6 +152,12 @@ struct PlayerController {
|
||||
};
|
||||
|
||||
namespace JsonSerializer {
|
||||
template<> void serialize<QPointF>( JsonWriter &jw, const QPointF &obj );
|
||||
template<> QPointF parse<QPointF>( const Json &json );
|
||||
|
||||
template<> void serialize<ControllerGateMapping>( JsonWriter &jw, const ControllerGateMapping &obj );
|
||||
template<> ControllerGateMapping parse<ControllerGateMapping>( const Json &json );
|
||||
|
||||
template<> void serialize<Binding>( JsonWriter &jw, const Binding &obj );
|
||||
template<> Binding parse<Binding>( const Json &json );
|
||||
|
||||
|
@@ -612,3 +612,44 @@ bool DefaultInputModes::exists( const Uuid &uuid ) {
|
||||
uuid == DefaultInputModes::GoldenEye.id ||
|
||||
uuid == DefaultInputModes::Clone.id;
|
||||
}
|
||||
|
||||
const ControllerGateMapping DefaultControllerGateMappings::Legacy = {
|
||||
"Legacy",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
QPointF( -1.0, -1.0 ),
|
||||
QPointF( 1.0, 1.0 ),
|
||||
{ QPointF(), QPointF(), QPointF(), QPointF() },
|
||||
0.0,
|
||||
0.15
|
||||
};
|
||||
|
||||
const ControllerGateMapping DefaultControllerGateMappings::Passthrough = {
|
||||
"Raw Input",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
QPointF( -1.0, -1.0 ),
|
||||
QPointF( 1.0, 1.0 ),
|
||||
{
|
||||
QPointF( 1.0, 1.0 ),
|
||||
QPointF( 1.0, -1.0 ),
|
||||
QPointF( -1.0, -1.0 ),
|
||||
QPointF( -1.0, 1.0 )
|
||||
},
|
||||
1.0,
|
||||
0.0
|
||||
};
|
||||
|
||||
const ControllerGateMapping DefaultControllerGateMappings::XBox = {
|
||||
"Default XBox/Playstation",
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
QPointF( -1.0, -1.0 ),
|
||||
QPointF( 1.0, 1.0 ),
|
||||
{ QPointF(), QPointF(), QPointF(), QPointF() },
|
||||
0.75,
|
||||
0.0
|
||||
};
|
||||
|
@@ -30,5 +30,10 @@ namespace DefaultInputModes {
|
||||
extern bool exists( const Uuid &uuid );
|
||||
}
|
||||
|
||||
namespace DefaultControllerGateMappings {
|
||||
extern const ControllerGateMapping Legacy;
|
||||
extern const ControllerGateMapping Passthrough;
|
||||
extern const ControllerGateMapping XBox;
|
||||
}
|
||||
|
||||
#endif /* SRC_CORE_PRESET_CONTROLLERS_HPP_ */
|
||||
|
167
src/ui/analog-stick-viewer.cpp
Normal file
167
src/ui/analog-stick-viewer.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#include "src/ui/analog-stick-viewer.hpp"
|
||||
|
||||
#include <QResizeEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QWindow>
|
||||
#include <QPointF>
|
||||
#include <QPoint>
|
||||
#include <QRectF>
|
||||
#include <QBrush>
|
||||
#include <QPen>
|
||||
|
||||
void AnalogStickViewer::paintEvent( QPaintEvent *paintEvent ) {
|
||||
QPainter( this ).drawPixmap( paintEvent->rect(), m_pixmap, paintEvent->rect() );
|
||||
}
|
||||
|
||||
void AnalogStickViewer::resizeEvent( QResizeEvent *resizeEvent ) {
|
||||
double dpr = 1.0;
|
||||
if( window() != nullptr ) {
|
||||
dpr = window()->devicePixelRatioF();
|
||||
if( dpr <= 0.0 ) dpr = 1.0;
|
||||
}
|
||||
|
||||
m_pixmap = QPixmap( (resizeEvent->size().toSizeF() * dpr).toSize() );
|
||||
m_pixmap.setDevicePixelRatio( dpr );
|
||||
clear();
|
||||
|
||||
QFrame::resizeEvent( resizeEvent );
|
||||
}
|
||||
|
||||
void AnalogStickViewer::onInput( double x, double y ) {
|
||||
QPainter painter( &m_pixmap );
|
||||
|
||||
if( !m_lastDot.isNull() ) {
|
||||
painter.fillRect( m_lastDot, Qt::blue );
|
||||
}
|
||||
|
||||
if( m_viewType == Calibration ) {
|
||||
clear();
|
||||
} else {
|
||||
paintGateOrDeadzone( true );
|
||||
}
|
||||
|
||||
const double dpr = m_pixmap.devicePixelRatioF();
|
||||
const int ds = (int)((4.0 * dpr) + 0.5);
|
||||
const QPointF centre( (double)m_pixmap.width() / 2.0, (double)m_pixmap.height() / 2.0 );
|
||||
|
||||
painter.setRenderHint( QPainter::Antialiasing, false );
|
||||
const double r = centre.x() - (2.0 * dpr);
|
||||
const QPoint p = (centre + (QPointF( x, y ) * r) + QPointF( -2.0 * dpr, -2.0 * dpr )).toPoint();
|
||||
m_lastDot = QRect( p, p + QPoint( ds, ds ) );
|
||||
if( m_viewType == PhysicalInputView && std::hypot( x, y ) < m_mapping.deadzoneSize ) {
|
||||
painter.fillRect( m_lastDot, Qt::gray );
|
||||
} else {
|
||||
painter.fillRect( m_lastDot, Qt::yellow );
|
||||
}
|
||||
}
|
||||
|
||||
void AnalogStickViewer::paintGateOrDeadzone( bool deadzone ) {
|
||||
if( deadzone && m_viewType != PhysicalInputView ) return;
|
||||
|
||||
QPainter painter( &m_pixmap );
|
||||
painter.setRenderHint( QPainter::Antialiasing, true );
|
||||
|
||||
const double dpr = m_pixmap.devicePixelRatioF();
|
||||
|
||||
if( deadzone ) {
|
||||
QPen stroke( QBrush( Qt::red, Qt::SolidPattern ), 2.0 * dpr, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin );
|
||||
painter.setPen( stroke );
|
||||
painter.setBrush( QBrush( Qt::black, Qt::SolidPattern ) );
|
||||
} else {
|
||||
QPen stroke( QBrush( Qt::green, Qt::SolidPattern ), 3.0 * dpr, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin );
|
||||
painter.setPen( stroke );
|
||||
painter.setBrush( QBrush() );
|
||||
}
|
||||
|
||||
const double padding = 2.0 * dpr;
|
||||
const double s = ((double)m_pixmap.width() - (2.0 * padding)) * (deadzone ? m_mapping.deadzoneSize : 1.0);
|
||||
const double r = s / 2.0;
|
||||
const QPointF centre( (double)m_pixmap.width() / 2.0, (double)m_pixmap.height() / 2.0 );
|
||||
|
||||
switch( m_viewType ) {
|
||||
case Calibration:
|
||||
case PhysicalInputView: {
|
||||
if( m_mapping.circularPhysicalGate ) {
|
||||
painter.drawArc(
|
||||
QRectF(
|
||||
centre.x() - m_mapping.physicalMax.x() * r,
|
||||
centre.y() - m_mapping.physicalMax.y() * r,
|
||||
m_mapping.physicalMax.x() * 2.0,
|
||||
m_mapping.physicalMax.y() * 2.0
|
||||
),
|
||||
0,
|
||||
1440
|
||||
);
|
||||
painter.drawArc(
|
||||
QRectF(
|
||||
centre.x() - m_mapping.physicalMax.x() * r,
|
||||
centre.y() + m_mapping.physicalMin.y() * r,
|
||||
m_mapping.physicalMax.x() * 2.0,
|
||||
m_mapping.physicalMin.y() * -2.0
|
||||
),
|
||||
1440,
|
||||
1440
|
||||
);
|
||||
painter.drawArc(
|
||||
QRectF(
|
||||
centre.x() + m_mapping.physicalMin.x() * r,
|
||||
centre.y() + m_mapping.physicalMin.y() * r,
|
||||
m_mapping.physicalMin.x() * -2.0,
|
||||
m_mapping.physicalMin.y() * -2.0
|
||||
),
|
||||
2880,
|
||||
1440
|
||||
);
|
||||
painter.drawArc(
|
||||
QRectF(
|
||||
centre.x() + m_mapping.physicalMin.x() * r,
|
||||
centre.y() - m_mapping.physicalMax.y() * r,
|
||||
m_mapping.physicalMin.x() * -2.0,
|
||||
m_mapping.physicalMax.y() * 2.0
|
||||
),
|
||||
4320,
|
||||
1440
|
||||
);
|
||||
} else {
|
||||
const QPointF gate[8] = {
|
||||
centre + QPointF( 0, m_mapping.physicalMax.y() ),
|
||||
centre + m_mapping.physicalNotches[0] * r,
|
||||
centre + QPointF( m_mapping.physicalMax.x(), 0 ),
|
||||
centre + m_mapping.physicalNotches[1] * r,
|
||||
centre + QPointF( 0, m_mapping.physicalMin.y() ),
|
||||
centre + m_mapping.physicalNotches[2] * r,
|
||||
centre + QPointF( m_mapping.physicalMin.x(), 0 ),
|
||||
centre + m_mapping.physicalNotches[3] * r
|
||||
};
|
||||
painter.drawPolygon( gate, 8 );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EmulatedInputView: {
|
||||
if( m_mapping.emulatedNotch == 0.0 ) {
|
||||
painter.drawEllipse( centre, r, r );
|
||||
} else {
|
||||
const QPointF gate[8] = {
|
||||
centre + QPointF( 0, r ),
|
||||
centre + QPointF( m_mapping.emulatedNotch * r, m_mapping.emulatedNotch * r ),
|
||||
centre + QPointF( r, 0 ),
|
||||
centre + QPointF( m_mapping.emulatedNotch * r, -m_mapping.emulatedNotch * r ),
|
||||
centre + QPointF( 0, -r ),
|
||||
centre + QPointF( -m_mapping.emulatedNotch * r, -m_mapping.emulatedNotch * r ),
|
||||
centre + QPointF( -r, 0 ),
|
||||
centre + QPointF( -m_mapping.emulatedNotch * r, m_mapping.emulatedNotch * r )
|
||||
};
|
||||
painter.drawPolygon( gate, 8 );
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void AnalogStickViewer::clear() {
|
||||
m_pixmap.fill( Qt::black );
|
||||
paintGateOrDeadzone( true );
|
||||
paintGateOrDeadzone( false );
|
||||
}
|
51
src/ui/analog-stick-viewer.hpp
Normal file
51
src/ui/analog-stick-viewer.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef SRC_UI_ANALOG_STICK_VIEWER_HPP_
|
||||
#define SRC_UI_ANALOG_STICK_VIEWER_HPP_
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QFrame>
|
||||
#include <QRect>
|
||||
#include "src/core/controller.hpp"
|
||||
|
||||
class AnalogStickViewer : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ViewType {
|
||||
Calibration = 0,
|
||||
PhysicalInputView = 1,
|
||||
EmulatedInputView = 2
|
||||
};
|
||||
|
||||
private:
|
||||
QPixmap m_pixmap;
|
||||
QRect m_lastDot;
|
||||
ControllerGateMapping m_mapping;
|
||||
ViewType m_viewType;
|
||||
|
||||
void paintGateOrDeadzone( bool deadzone );
|
||||
|
||||
public:
|
||||
inline AnalogStickViewer( QWidget *parent = nullptr ) : QFrame( parent ) {}
|
||||
inline ~AnalogStickViewer() {};
|
||||
|
||||
inline void setMapping( const ControllerGateMapping &mapping ) {
|
||||
m_mapping = mapping;
|
||||
clear();
|
||||
}
|
||||
|
||||
inline void setViewType( ViewType type ) {
|
||||
m_viewType = type;
|
||||
clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent( QPaintEvent *paintEvent ) override;
|
||||
void resizeEvent( QResizeEvent *resizeEvent ) override;
|
||||
|
||||
public slots:
|
||||
void onInput( double x, double y );
|
||||
void clear();
|
||||
|
||||
};
|
||||
|
||||
#endif /* SRC_UI_ANALOG_STICK_VIEWER_HPP_ */
|
Reference in New Issue
Block a user