@nativescript/canvas-gamepad 3.0.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ package org.nativescript.canvas.gamepad;
2
+
3
+ import android.os.Build;
4
+ import android.view.ActionMode;
5
+ import android.view.KeyEvent;
6
+ import android.view.KeyboardShortcutGroup;
7
+ import android.view.Menu;
8
+ import android.view.MenuItem;
9
+ import android.view.MotionEvent;
10
+ import android.view.SearchEvent;
11
+ import android.view.View;
12
+ import android.view.Window;
13
+ import android.view.WindowManager;
14
+ import android.view.accessibility.AccessibilityEvent;
15
+
16
+ import androidx.annotation.NonNull;
17
+ import androidx.annotation.Nullable;
18
+ import androidx.annotation.RequiresApi;
19
+
20
+ import java.util.List;
21
+
22
+ // Joystick events only reach the focused view.
23
+ final class GamepadWindowCallback implements Window.Callback {
24
+ private final Window.Callback wrapped;
25
+
26
+ GamepadWindowCallback(Window.Callback wrapped) {
27
+ this.wrapped = wrapped;
28
+ }
29
+
30
+ @Override
31
+ public boolean dispatchKeyEvent(KeyEvent event) {
32
+ return NSCGamepadManager.onKeyEvent(event) || wrapped.dispatchKeyEvent(event);
33
+ }
34
+
35
+ @Override
36
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
37
+ return NSCGamepadManager.onMotionEvent(event) || wrapped.dispatchGenericMotionEvent(event);
38
+ }
39
+
40
+ @Override
41
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
42
+ return wrapped.dispatchKeyShortcutEvent(event);
43
+ }
44
+
45
+ @Override
46
+ public boolean dispatchTouchEvent(MotionEvent event) {
47
+ return wrapped.dispatchTouchEvent(event);
48
+ }
49
+
50
+ @Override
51
+ public boolean dispatchTrackballEvent(MotionEvent event) {
52
+ return wrapped.dispatchTrackballEvent(event);
53
+ }
54
+
55
+ @Override
56
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
57
+ return wrapped.dispatchPopulateAccessibilityEvent(event);
58
+ }
59
+
60
+ @Nullable
61
+ @Override
62
+ public View onCreatePanelView(int featureId) {
63
+ return wrapped.onCreatePanelView(featureId);
64
+ }
65
+
66
+ @Override
67
+ public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
68
+ return wrapped.onCreatePanelMenu(featureId, menu);
69
+ }
70
+
71
+ @Override
72
+ public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
73
+ return wrapped.onPreparePanel(featureId, view, menu);
74
+ }
75
+
76
+ @Override
77
+ public boolean onMenuOpened(int featureId, @NonNull Menu menu) {
78
+ return wrapped.onMenuOpened(featureId, menu);
79
+ }
80
+
81
+ @Override
82
+ public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
83
+ return wrapped.onMenuItemSelected(featureId, item);
84
+ }
85
+
86
+ @Override
87
+ public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
88
+ wrapped.onWindowAttributesChanged(attrs);
89
+ }
90
+
91
+ @Override
92
+ public void onContentChanged() {
93
+ wrapped.onContentChanged();
94
+ }
95
+
96
+ @Override
97
+ public void onWindowFocusChanged(boolean hasFocus) {
98
+ wrapped.onWindowFocusChanged(hasFocus);
99
+ }
100
+
101
+ @Override
102
+ public void onAttachedToWindow() {
103
+ wrapped.onAttachedToWindow();
104
+ }
105
+
106
+ @Override
107
+ public void onDetachedFromWindow() {
108
+ wrapped.onDetachedFromWindow();
109
+ }
110
+
111
+ @Override
112
+ public void onPanelClosed(int featureId, @NonNull Menu menu) {
113
+ wrapped.onPanelClosed(featureId, menu);
114
+ }
115
+
116
+ @Override
117
+ public boolean onSearchRequested() {
118
+ return wrapped.onSearchRequested();
119
+ }
120
+
121
+ @RequiresApi(Build.VERSION_CODES.M)
122
+ @Override
123
+ public boolean onSearchRequested(SearchEvent searchEvent) {
124
+ return wrapped.onSearchRequested(searchEvent);
125
+ }
126
+
127
+ @Nullable
128
+ @Override
129
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
130
+ return wrapped.onWindowStartingActionMode(callback);
131
+ }
132
+
133
+ @RequiresApi(Build.VERSION_CODES.M)
134
+ @Nullable
135
+ @Override
136
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
137
+ return wrapped.onWindowStartingActionMode(callback, type);
138
+ }
139
+
140
+ @Override
141
+ public void onActionModeStarted(ActionMode mode) {
142
+ wrapped.onActionModeStarted(mode);
143
+ }
144
+
145
+ @Override
146
+ public void onActionModeFinished(ActionMode mode) {
147
+ wrapped.onActionModeFinished(mode);
148
+ }
149
+
150
+ @RequiresApi(Build.VERSION_CODES.N)
151
+ @Override
152
+ public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) {
153
+ wrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
154
+ }
155
+
156
+ @RequiresApi(Build.VERSION_CODES.O)
157
+ @Override
158
+ public void onPointerCaptureChanged(boolean hasCapture) {
159
+ wrapped.onPointerCaptureChanged(hasCapture);
160
+ }
161
+ }
@@ -0,0 +1,367 @@
1
+ package org.nativescript.canvas.gamepad;
2
+
3
+ import android.app.Activity;
4
+ import android.app.Application;
5
+ import android.content.Context;
6
+ import android.hardware.input.InputManager;
7
+ import android.os.Bundle;
8
+ import android.os.Handler;
9
+ import android.os.Looper;
10
+ import android.view.InputDevice;
11
+ import android.view.KeyEvent;
12
+ import android.view.MotionEvent;
13
+ import android.view.Window;
14
+
15
+ import androidx.annotation.NonNull;
16
+ import androidx.annotation.Nullable;
17
+
18
+ import java.nio.FloatBuffer;
19
+ import java.util.Locale;
20
+
21
+ public final class NSCGamepadManager {
22
+ public interface Listener {
23
+ void onConnection(int index, boolean connected, String id);
24
+ }
25
+
26
+ // Keep in sync with packages/canvas-gamepad/common.ts.
27
+ static final int MAX_GAMEPADS = 4;
28
+ static final int MAX_AXES = 8;
29
+ static final int SLOT_CONNECTED = 0;
30
+ static final int SLOT_SEQUENCE = 1;
31
+ static final int SLOT_MAPPING = 2;
32
+ static final int SLOT_AXIS_COUNT = 3;
33
+ static final int SLOT_BUTTON_COUNT = 4;
34
+ static final int SLOT_AXES = 5;
35
+ static final int SLOT_BUTTONS = SLOT_AXES + MAX_AXES;
36
+ static final int SLOT_STRIDE = 80;
37
+ static final int BUTTON_PRESSED = 1;
38
+ static final int BUTTON_TOUCHED = 2;
39
+ static final float MAPPING_STANDARD = 1;
40
+ static final int STANDARD_AXES = 4;
41
+ static final int STANDARD_BUTTONS = 17;
42
+ // Float32 holds integers exactly up to 2^24.
43
+ static final float SEQUENCE_WRAP = 16777216f;
44
+ static final float TRIGGER_PRESSED = 0.1f;
45
+
46
+ static final int BUTTON_LEFT_TRIGGER = 6;
47
+ static final int BUTTON_RIGHT_TRIGGER = 7;
48
+ static final int BUTTON_DPAD_UP = 12;
49
+ static final int BUTTON_DPAD_DOWN = 13;
50
+ static final int BUTTON_DPAD_LEFT = 14;
51
+ static final int BUTTON_DPAD_RIGHT = 15;
52
+
53
+ private static final class Slot {
54
+ final int deviceId;
55
+ final boolean hasHat;
56
+ final boolean hasAnalogTriggers;
57
+ final int rightX;
58
+ final int rightY;
59
+
60
+ Slot(InputDevice device) {
61
+ deviceId = device.getId();
62
+ hasHat = device.getMotionRange(MotionEvent.AXIS_HAT_X) != null;
63
+ hasAnalogTriggers = device.getMotionRange(MotionEvent.AXIS_LTRIGGER) != null || device.getMotionRange(MotionEvent.AXIS_BRAKE) != null;
64
+ boolean z = device.getMotionRange(MotionEvent.AXIS_Z) != null;
65
+ boolean rx = device.getMotionRange(MotionEvent.AXIS_RX) != null;
66
+ rightX = !z && rx ? MotionEvent.AXIS_RX : MotionEvent.AXIS_Z;
67
+ rightY = !z && rx ? MotionEvent.AXIS_RY : MotionEvent.AXIS_RZ;
68
+ }
69
+ }
70
+
71
+ private static final Slot[] slots = new Slot[MAX_GAMEPADS];
72
+ @Nullable
73
+ private static FloatBuffer buffer;
74
+ @Nullable
75
+ private static Listener listener;
76
+ @Nullable
77
+ private static InputManager inputManager;
78
+ @Nullable
79
+ private static Application application;
80
+
81
+ private static final InputManager.InputDeviceListener deviceListener = new InputManager.InputDeviceListener() {
82
+ @Override
83
+ public void onInputDeviceAdded(int deviceId) {
84
+ attach(deviceId);
85
+ }
86
+
87
+ @Override
88
+ public void onInputDeviceRemoved(int deviceId) {
89
+ detach(deviceId);
90
+ }
91
+
92
+ @Override
93
+ public void onInputDeviceChanged(int deviceId) {
94
+ }
95
+ };
96
+
97
+ private static final Application.ActivityLifecycleCallbacks lifecycle = new Application.ActivityLifecycleCallbacks() {
98
+ @Override
99
+ public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
100
+ hook(activity);
101
+ }
102
+
103
+ @Override
104
+ public void onActivityStarted(@NonNull Activity activity) {
105
+ }
106
+
107
+ @Override
108
+ public void onActivityResumed(@NonNull Activity activity) {
109
+ hook(activity);
110
+ }
111
+
112
+ @Override
113
+ public void onActivityPaused(@NonNull Activity activity) {
114
+ }
115
+
116
+ @Override
117
+ public void onActivityStopped(@NonNull Activity activity) {
118
+ }
119
+
120
+ @Override
121
+ public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
122
+ }
123
+
124
+ @Override
125
+ public void onActivityDestroyed(@NonNull Activity activity) {
126
+ }
127
+ };
128
+
129
+ private NSCGamepadManager() {
130
+ }
131
+
132
+ public static void start(@NonNull Context context, @Nullable Activity activity, @NonNull FloatBuffer buffer, @NonNull Listener listener) {
133
+ stop();
134
+ if (buffer.capacity() < MAX_GAMEPADS * SLOT_STRIDE) {
135
+ return;
136
+ }
137
+ NSCGamepadManager.buffer = buffer;
138
+ NSCGamepadManager.listener = listener;
139
+ for (int i = 0; i < MAX_GAMEPADS * SLOT_STRIDE; i++) {
140
+ buffer.put(i, 0);
141
+ }
142
+
143
+ Context app = context.getApplicationContext();
144
+ if (app instanceof Application) {
145
+ application = (Application) app;
146
+ application.registerActivityLifecycleCallbacks(lifecycle);
147
+ }
148
+ if (activity != null) {
149
+ hook(activity);
150
+ }
151
+
152
+ inputManager = (InputManager) app.getSystemService(Context.INPUT_SERVICE);
153
+ if (inputManager != null) {
154
+ inputManager.registerInputDeviceListener(deviceListener, new Handler(Looper.getMainLooper()));
155
+ for (int id : inputManager.getInputDeviceIds()) {
156
+ attach(id);
157
+ }
158
+ }
159
+ }
160
+
161
+ public static void stop() {
162
+ if (inputManager != null) {
163
+ inputManager.unregisterInputDeviceListener(deviceListener);
164
+ inputManager = null;
165
+ }
166
+ if (application != null) {
167
+ application.unregisterActivityLifecycleCallbacks(lifecycle);
168
+ application = null;
169
+ }
170
+ for (int i = 0; i < MAX_GAMEPADS; i++) {
171
+ slots[i] = null;
172
+ }
173
+ buffer = null;
174
+ listener = null;
175
+ }
176
+
177
+ static boolean isGamepad(int sources) {
178
+ return (sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD || (sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK;
179
+ }
180
+
181
+ private static void hook(Activity activity) {
182
+ Window window = activity.getWindow();
183
+ if (window == null) {
184
+ return;
185
+ }
186
+ Window.Callback callback = window.getCallback();
187
+ if (callback != null && !(callback instanceof GamepadWindowCallback)) {
188
+ window.setCallback(new GamepadWindowCallback(callback));
189
+ }
190
+ }
191
+
192
+ private static int slotOf(int deviceId) {
193
+ for (int i = 0; i < MAX_GAMEPADS; i++) {
194
+ Slot slot = slots[i];
195
+ if (slot != null && slot.deviceId == deviceId) {
196
+ return i;
197
+ }
198
+ }
199
+ return -1;
200
+ }
201
+
202
+ private static void attach(int deviceId) {
203
+ FloatBuffer buffer = NSCGamepadManager.buffer;
204
+ InputDevice device = InputDevice.getDevice(deviceId);
205
+ if (buffer == null || device == null || device.isVirtual() || !isGamepad(device.getSources()) || slotOf(deviceId) != -1) {
206
+ return;
207
+ }
208
+ int index = -1;
209
+ for (int i = 0; i < MAX_GAMEPADS; i++) {
210
+ if (slots[i] == null) {
211
+ index = i;
212
+ break;
213
+ }
214
+ }
215
+ if (index == -1) {
216
+ return;
217
+ }
218
+ slots[index] = new Slot(device);
219
+
220
+ int base = index * SLOT_STRIDE;
221
+ for (int i = 0; i < SLOT_STRIDE; i++) {
222
+ buffer.put(base + i, 0);
223
+ }
224
+ buffer.put(base + SLOT_CONNECTED, 1);
225
+ buffer.put(base + SLOT_MAPPING, MAPPING_STANDARD);
226
+ buffer.put(base + SLOT_AXIS_COUNT, STANDARD_AXES);
227
+ buffer.put(base + SLOT_BUTTON_COUNT, STANDARD_BUTTONS);
228
+
229
+ if (listener != null) {
230
+ String id = String.format(Locale.US, "%s (STANDARD GAMEPAD Vendor: %04x Product: %04x)", device.getName(), device.getVendorId(), device.getProductId());
231
+ listener.onConnection(index, true, id);
232
+ }
233
+ }
234
+
235
+ private static void detach(int deviceId) {
236
+ FloatBuffer buffer = NSCGamepadManager.buffer;
237
+ int index = slotOf(deviceId);
238
+ if (buffer == null || index == -1) {
239
+ return;
240
+ }
241
+ slots[index] = null;
242
+ int base = index * SLOT_STRIDE;
243
+ for (int i = 0; i < SLOT_STRIDE; i++) {
244
+ buffer.put(base + i, 0);
245
+ }
246
+ if (listener != null) {
247
+ listener.onConnection(index, false, "");
248
+ }
249
+ }
250
+
251
+ static int buttonForKey(int keyCode) {
252
+ switch (keyCode) {
253
+ case KeyEvent.KEYCODE_BUTTON_A:
254
+ return 0;
255
+ case KeyEvent.KEYCODE_BUTTON_B:
256
+ return 1;
257
+ case KeyEvent.KEYCODE_BUTTON_X:
258
+ return 2;
259
+ case KeyEvent.KEYCODE_BUTTON_Y:
260
+ return 3;
261
+ case KeyEvent.KEYCODE_BUTTON_L1:
262
+ return 4;
263
+ case KeyEvent.KEYCODE_BUTTON_R1:
264
+ return 5;
265
+ case KeyEvent.KEYCODE_BUTTON_L2:
266
+ return BUTTON_LEFT_TRIGGER;
267
+ case KeyEvent.KEYCODE_BUTTON_R2:
268
+ return BUTTON_RIGHT_TRIGGER;
269
+ case KeyEvent.KEYCODE_BUTTON_SELECT:
270
+ return 8;
271
+ case KeyEvent.KEYCODE_BUTTON_START:
272
+ return 9;
273
+ case KeyEvent.KEYCODE_BUTTON_THUMBL:
274
+ return 10;
275
+ case KeyEvent.KEYCODE_BUTTON_THUMBR:
276
+ return 11;
277
+ case KeyEvent.KEYCODE_DPAD_UP:
278
+ return BUTTON_DPAD_UP;
279
+ case KeyEvent.KEYCODE_DPAD_DOWN:
280
+ return BUTTON_DPAD_DOWN;
281
+ case KeyEvent.KEYCODE_DPAD_LEFT:
282
+ return BUTTON_DPAD_LEFT;
283
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
284
+ return BUTTON_DPAD_RIGHT;
285
+ case KeyEvent.KEYCODE_BUTTON_MODE:
286
+ return 16;
287
+ default:
288
+ return -1;
289
+ }
290
+ }
291
+
292
+ private static void writeButton(FloatBuffer buffer, int base, int button, float value, boolean pressed) {
293
+ int offset = base + SLOT_BUTTONS + button * 2;
294
+ buffer.put(offset, value);
295
+ buffer.put(offset + 1, (pressed ? BUTTON_PRESSED : 0) | (pressed || value > 0 ? BUTTON_TOUCHED : 0));
296
+ }
297
+
298
+ private static void bump(FloatBuffer buffer, int base) {
299
+ buffer.put(base + SLOT_SEQUENCE, (buffer.get(base + SLOT_SEQUENCE) + 1) % SEQUENCE_WRAP);
300
+ }
301
+
302
+ static boolean onKeyEvent(KeyEvent event) {
303
+ FloatBuffer buffer = NSCGamepadManager.buffer;
304
+ if (buffer == null || !isGamepad(event.getSource())) {
305
+ return false;
306
+ }
307
+ int index = slotOf(event.getDeviceId());
308
+ int button = buttonForKey(event.getKeyCode());
309
+ if (index == -1 || button == -1) {
310
+ return false;
311
+ }
312
+ int action = event.getAction();
313
+ if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP) {
314
+ return true;
315
+ }
316
+ if (event.getRepeatCount() > 0) {
317
+ return true;
318
+ }
319
+ Slot slot = slots[index];
320
+ boolean analogTrigger = slot.hasAnalogTriggers && (button == BUTTON_LEFT_TRIGGER || button == BUTTON_RIGHT_TRIGGER);
321
+ boolean hatDpad = slot.hasHat && button >= BUTTON_DPAD_UP && button <= BUTTON_DPAD_RIGHT;
322
+ if (!analogTrigger && !hatDpad) {
323
+ int base = index * SLOT_STRIDE;
324
+ boolean down = action == KeyEvent.ACTION_DOWN;
325
+ writeButton(buffer, base, button, down ? 1 : 0, down);
326
+ bump(buffer, base);
327
+ }
328
+ return true;
329
+ }
330
+
331
+ static boolean onMotionEvent(MotionEvent event) {
332
+ FloatBuffer buffer = NSCGamepadManager.buffer;
333
+ if (buffer == null || (event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK || event.getActionMasked() != MotionEvent.ACTION_MOVE) {
334
+ return false;
335
+ }
336
+ int index = slotOf(event.getDeviceId());
337
+ if (index == -1) {
338
+ return false;
339
+ }
340
+ Slot slot = slots[index];
341
+ int base = index * SLOT_STRIDE;
342
+
343
+ buffer.put(base + SLOT_AXES, event.getAxisValue(MotionEvent.AXIS_X));
344
+ buffer.put(base + SLOT_AXES + 1, event.getAxisValue(MotionEvent.AXIS_Y));
345
+ buffer.put(base + SLOT_AXES + 2, event.getAxisValue(slot.rightX));
346
+ buffer.put(base + SLOT_AXES + 3, event.getAxisValue(slot.rightY));
347
+
348
+ if (slot.hasAnalogTriggers) {
349
+ float left = Math.max(event.getAxisValue(MotionEvent.AXIS_LTRIGGER), event.getAxisValue(MotionEvent.AXIS_BRAKE));
350
+ float right = Math.max(event.getAxisValue(MotionEvent.AXIS_RTRIGGER), event.getAxisValue(MotionEvent.AXIS_GAS));
351
+ writeButton(buffer, base, BUTTON_LEFT_TRIGGER, left, left > TRIGGER_PRESSED);
352
+ writeButton(buffer, base, BUTTON_RIGHT_TRIGGER, right, right > TRIGGER_PRESSED);
353
+ }
354
+
355
+ if (slot.hasHat) {
356
+ float x = event.getAxisValue(MotionEvent.AXIS_HAT_X);
357
+ float y = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
358
+ writeButton(buffer, base, BUTTON_DPAD_UP, y < -0.5f ? 1 : 0, y < -0.5f);
359
+ writeButton(buffer, base, BUTTON_DPAD_DOWN, y > 0.5f ? 1 : 0, y > 0.5f);
360
+ writeButton(buffer, base, BUTTON_DPAD_LEFT, x < -0.5f ? 1 : 0, x < -0.5f);
361
+ writeButton(buffer, base, BUTTON_DPAD_RIGHT, x > 0.5f ? 1 : 0, x > 0.5f);
362
+ }
363
+
364
+ bump(buffer, base);
365
+ return true;
366
+ }
367
+ }
@@ -0,0 +1 @@
1
+ OTHER_LDFLAGS = $(inherited) -framework "GameController"
@@ -0,0 +1 @@
1
+ #import "NSCGamepadManager.h"
@@ -0,0 +1,17 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ typedef void (^NSCGamepadConnectionListener)(NSInteger index, BOOL connected, NSString *gamepadId);
6
+
7
+ @interface NSCGamepadManager : NSObject
8
+
9
+ @property (class, nonatomic, readonly, strong) NSCGamepadManager *shared;
10
+
11
+ - (void)startWithBuffer:(void *)buffer length:(NSInteger)length listener:(NSCGamepadConnectionListener)listener;
12
+
13
+ - (void)stop;
14
+
15
+ @end
16
+
17
+ NS_ASSUME_NONNULL_END