Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions packages/gamepads/lib/src/mappings/android_mapping.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class AndroidMapping extends PlatformMapping {
'AXIS_RTRIGGER': GamepadAxis.rightTrigger,
'AXIS_BRAKE': GamepadAxis.leftTrigger,
'AXIS_GAS': GamepadAxis.rightTrigger,
// Right-stick axes for non-Xbox layouts, which report the right stick on
// RX/RY instead of Z/RZ. E.g. the DJI RC Pro.
'AXIS_RX': GamepadAxis.rightStickX,
'AXIS_RY': GamepadAxis.rightStickY,
};

// D-pad hat axes on Android.
Expand All @@ -59,10 +63,9 @@ class AndroidMapping extends PlatformMapping {
return const [];
}
// Android reports sticks in -1.0 to 1.0 and triggers in 0.0 to 1.0.
// Y-axis is inverted on Android (up = negative).
if (axis == GamepadAxis.leftStickY || axis == GamepadAxis.rightStickY) {
return [NormalizedAxis(axis, -value)];
}
// gamepads_android's EventListener already inverts the stick Y axes
// (AXIS_Y, AXIS_RZ and AXIS_RY), so the values reaching here follow
// up = +1.0, down = -1.0 and must not be negated again. See issue #123.
return [NormalizedAxis(axis, value)];
}

Expand All @@ -78,9 +81,8 @@ class AndroidMapping extends PlatformMapping {
];
}
if (key == _dpadYAxis) {
// gamepads_android's EventListener already inverts AXIS_HAT_Y, so the
// value reaching here follows up = +1.0, down = -1.0 (the opposite of
// Android's native AXIS_HAT_Y convention). See issue #123.
// As with the stick Y axes above, EventListener already inverts
// AXIS_HAT_Y, so up = +1.0 and down = -1.0 here.
return [
NormalizedButton(
GamepadButton.dpadDown,
Expand Down
8 changes: 5 additions & 3 deletions packages/gamepads/lib/src/mappings/platform_mapping.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ class NormalizedAxis {
///
/// **Y-axis convention**: Normalized stick Y values use up = +1.0,
/// down = -1.0. Platforms where the native API reports the opposite
/// (e.g., iOS, Android, Web) must negate Y in their [normalizeAxis].
/// (e.g., iOS, Web) must negate Y in their [normalizeAxis].
/// macOS GCController already reports up = positive natively, so no
/// inversion is needed there. Linux/Windows handle inversion via the
/// `yAxisInverted` flag in `ControllerMapping`.
/// inversion is needed there. Android is inverted by the
/// `gamepads_android` plugin before the event reaches Dart, so its
/// mapping must not negate Y again. Linux/Windows handle inversion via
/// the `yAxisInverted` flag in `ControllerMapping`.
abstract class PlatformMapping {
/// Attempts to normalize a button event.
///
Expand Down
4 changes: 2 additions & 2 deletions packages/gamepads/test/gamepad_normalizer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ void main() {
expect(results.first.button, GamepadButton.a);
});

test('normalizes Android axis with Y inversion', () {
test('keeps the Android Y axis inverted by the plugin', () {
final event = GamepadEvent(
gamepadId: 'pad1',
timestamp: 1000,
Expand All @@ -105,7 +105,7 @@ void main() {

final results = normalizer.normalize(event);
expect(results.first.axis, GamepadAxis.leftStickY);
expect(results.first.value, -1.0);
expect(results.first.value, 1.0);
});
});

Expand Down
23 changes: 17 additions & 6 deletions packages/gamepads/test/mappings_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,18 +202,29 @@ void main() {
);
});

test('normalizes stick axes with Y inversion', () {
test('normalizes stick axes without inverting Y again', () {
final lx = mapping.normalizeAxis('AXIS_X', 0.5);
expect(lx.first.axis, GamepadAxis.leftStickX);
expect(lx.first.value, 0.5);

// Y-axis should be inverted
// EventListener already inverted AXIS_Y and AXIS_RZ, so a positive
// value here means up and must be passed through unchanged.
final ly = mapping.normalizeAxis('AXIS_Y', 0.5);
expect(ly.first.axis, GamepadAxis.leftStickY);
expect(ly.first.value, -0.5);
expect(ly.first.value, 0.5);

final ry = mapping.normalizeAxis('AXIS_RZ', -1.0);
expect(ry.first.axis, GamepadAxis.rightStickY);
expect(ry.first.value, -1.0);
});

test('normalizes the alternate right stick axes', () {
final rx = mapping.normalizeAxis('AXIS_RX', 0.5);
expect(rx.first.axis, GamepadAxis.rightStickX);
expect(rx.first.value, 0.5);

final ry = mapping.normalizeAxis('AXIS_RY', 1.0);
expect(ry.first.axis, GamepadAxis.rightStickY);
expect(ry.first.value, 1.0);
});

Expand Down Expand Up @@ -245,9 +256,9 @@ void main() {
expect(right[1].button, GamepadButton.dpadRight);
expect(right[1].value, 1.0);

// gamepads_android's EventListener inverts AXIS_HAT_Y before it reaches
// the mapping, so +1.0 = up and -1.0 = down here (regression for #123:
// pressing up must emit dpadUp, not dpadDown).
// EventListener inverts AXIS_HAT_Y before it reaches the mapping, so
// +1.0 = up and -1.0 = down here (regression for #123: pressing up
// must emit dpadUp, not dpadDown).
final up = mapping.normalizeDpadAxis('AXIS_HAT_Y', 1.0);
expect(up[0].button, GamepadButton.dpadDown);
expect(up[0].value, 0.0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ class EventListener {
}
private val lastAxisValue = mutableMapOf<Int, Float>()
// Reference: https://developer.android.com/reference/android/view/MotionEvent
//
// The vertical axes are inverted here so that the values sent to Dart
// follow up = +1.0, down = -1.0 instead of Android's native convention.
// AndroidMapping in the gamepads package relies on that and does not
// invert them again, so removing an `invert` below silently flips the
// corresponding stick or d-pad direction. See issue #123.
private val supportedAxes = listOf(
SupportedAxis(MotionEvent.AXIS_X),
SupportedAxis(MotionEvent.AXIS_Y, invert = true),
Expand Down
Loading