react-native-earl-gamepad 0.2.0 → 0.4.0

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.
package/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # react-native-earl-gamepad
2
2
 
3
- Lightweight, WebView-based Gamepad bridge for React Native that surfaces all common buttons, sticks, d-pad, and connect/disconnect events.
3
+ WebView-based gamepad bridge for React Native. Polls `navigator.getGamepads()` in a hidden WebView and surfaces buttons, sticks, d-pad, and connection events to JS.
4
4
 
5
- ## Features
5
+ - Components: `GamepadBridge`, `useGamepad`, and `GamepadDebug`.
6
+ - Deadzone handling (default `0.15`) with auto-clear on disconnect.
7
+ - Typed events for buttons, axes, d-pad, and status.
6
8
 
7
- - Hidden WebView polling `navigator.getGamepads()` (index 0) and emitting button/axis/status events.
8
- - `GamepadBridge` component, `useGamepad` hook, and `GamepadDebug` visual tester.
9
- - Deadzone handling for sticks (default 0.15) and auto-clear on disconnect.
9
+ ## Requirements
10
+
11
+ - React Native `>=0.72`
12
+ - React `>=18`
13
+ - `react-native-webview` `>=13`
14
+ - Runs on iOS and Android (relies on WebView Gamepad API support).
10
15
 
11
16
  ## Install
12
17
 
@@ -16,91 +21,130 @@ npm install react-native-earl-gamepad react-native-webview
16
21
  yarn add react-native-earl-gamepad react-native-webview
17
22
  ```
18
23
 
19
- ## Quick start
24
+ ## Usage
25
+
26
+ ### Render the bridge (minimal)
27
+
28
+ Render the hidden WebView once in your tree to start polling the first connected pad (`navigator.getGamepads()[0]`).
20
29
 
21
30
  ```tsx
22
- import { GamepadBridge } from 'react-native-earl-gamepad';
31
+ import { GamepadBridge } from "react-native-earl-gamepad";
23
32
 
24
33
  export function Controls() {
25
- return (
26
- <GamepadBridge
27
- enabled
28
- onButton={(e) => console.log('button', e.button, e.pressed, e.value)}
29
- onAxis={(e) => console.log('axis', e.axis, e.value)}
30
- onDpad={(e) => console.log('dpad', e.key, e.pressed)}
31
- onStatus={(s) => console.log('status', s.state)}
32
- />
33
- );
34
+ return (
35
+ <GamepadBridge
36
+ enabled
37
+ onButton={(e) =>
38
+ console.log("button", e.button, e.pressed, e.value)
39
+ }
40
+ onAxis={(e) => console.log("axis", e.axis, e.value)}
41
+ onDpad={(e) => console.log("dpad", e.key, e.pressed)}
42
+ onStatus={(e) => console.log("status", e.state)}
43
+ />
44
+ );
34
45
  }
35
46
  ```
36
47
 
37
- ### Using the hook
48
+ ### Hook for stateful consumption
49
+
50
+ `useGamepad` keeps pressed state and axes for you. You still need to render the provided `bridge` element once.
38
51
 
39
52
  ```tsx
40
- import { useGamepad } from 'react-native-earl-gamepad';
53
+ import { useGamepad } from "react-native-earl-gamepad";
41
54
 
42
55
  export function HUD() {
43
- const { pressedButtons, axes, isPressed, bridge } = useGamepad({ enabled: true });
44
-
45
- return (
46
- <>
47
- {bridge}
48
- <Text>Pressed: {Array.from(pressedButtons).join(', ') || 'none'}</Text>
49
- <Text>
50
- Left stick: x {axes.leftX?.toFixed(2)} / y {axes.leftY?.toFixed(2)}
51
- </Text>
52
- <Text>A held? {isPressed('a') ? 'yes' : 'no'}</Text>
53
- </>
54
- );
56
+ const { pressedButtons, axes, isPressed, bridge } = useGamepad({
57
+ enabled: true,
58
+ });
59
+
60
+ return (
61
+ <>
62
+ {bridge}
63
+ <Text>
64
+ Pressed: {Array.from(pressedButtons).join(", ") || "none"}
65
+ </Text>
66
+ <Text>
67
+ Left stick: x {axes.leftX?.toFixed(2)} / y{" "}
68
+ {axes.leftY?.toFixed(2)}
69
+ </Text>
70
+ <Text>A held? {isPressed("a") ? "yes" : "no"}</Text>
71
+ </>
72
+ );
55
73
  }
56
74
  ```
57
75
 
58
76
  ### Visual debugger
59
77
 
78
+ Drop-in component to see a controller diagram that lights up buttons, shows stick offsets, and lists state.
79
+
60
80
  ```tsx
61
- import { GamepadDebug } from 'react-native-earl-gamepad';
81
+ import { GamepadDebug } from "react-native-earl-gamepad";
62
82
 
63
83
  export function DebugScreen() {
64
- return <GamepadDebug />;
84
+ return <GamepadDebug axisThreshold={0.2} />;
65
85
  }
66
86
  ```
67
87
 
68
- `GamepadDebug` renders a controller diagram that lights up buttons, shows stick offsets, and lists pressed/axes values.
88
+ ![Gamepad visual idle](https://github.com/user-attachments/assets/dfebd8c5-7d9a-42c7-802b-2773ec8c8ae9)
89
+
90
+ ![Gamepad visual pressed](https://github.com/user-attachments/assets/7b37d76a-7695-4be9-bda4-7e3d1e6adf41)
69
91
 
70
92
  ## API
71
93
 
72
94
  ### `GamepadBridge` props
73
95
 
74
- - `enabled?: boolean` — mount/unmount the hidden WebView. Default `true`.
75
- - `axisThreshold?: number` — deadzone for axes. Default `0.15`.
76
- - `onButton?: (event)` — `{ type:'button', button, index, pressed, value }`.
77
- - `onAxis?: (event)` — `{ type:'axis', axis, index, value }`.
78
- - `onDpad?: (event)` `{ type:'dpad', key, pressed }` convenience mapped from buttons 12–15.
79
- - `onStatus?: (event)` — `{ type:'status', state:'connected'|'disconnected' }`.
80
- - `style?: StyleProp<ViewStyle>` — optional override; default is a 1×1 transparent view.
96
+ - `enabled?: boolean` — mount/unmount the hidden WebView. Default `true`.
97
+ - `axisThreshold?: number` — deadzone applied to axes. Default `0.15`.
98
+ - `onButton?: (event: ButtonEvent) => void` — fired on button press/release/value change.
99
+ - `onAxis?: (event: AxisEvent) => void` — fired when an axis changes beyond threshold.
100
+ - `onDpad?: (event: DpadEvent) => void` convenience mapping of button indices 12–15.
101
+ - `onStatus?: (event: StatusEvent) => void` — `connected` / `disconnected` events.
102
+ - `style?: StyleProp<ViewStyle>` — override container; default is a 1×1 transparent view.
81
103
 
82
- ### `useGamepad` return
104
+ ### `useGamepad` options and return
83
105
 
84
- - `pressedButtons: Set<GamepadButtonName>` — current pressed buttons (named or `button-N`).
85
- - `axes: Partial<Record<StickAxisName, number>>` — stick/axis values with deadzone applied.
86
- - `isPressed(key)`helper to query a button.
87
- - `bridge: JSX.Element | null` — render once in your tree to enable polling.
106
+ Options:
107
+
108
+ - `enabled?: boolean` — defaults to `true`. When false, state resets and axes zero out.
109
+ - `axisThreshold?: number` — deadzone for axes. Default `0.15`.
110
+ - `onButton`, `onAxis`, `onDpad`, `onStatus` — same semantics as `GamepadBridge`.
111
+
112
+ Return shape:
113
+
114
+ - `pressedButtons: Set<GamepadButtonName>` — current pressed buttons.
115
+ - `axes: Partial<Record<StickAxisName, number>>` — axis values with deadzone applied.
116
+ - `buttonValues: Partial<Record<GamepadButtonName, number>>` — last analog value per button (useful for LT/RT triggers).
117
+ - `isPressed(key: GamepadButtonName): boolean` — helper to check a single button.
118
+ - `bridge: JSX.Element | null` — render once to enable polling.
88
119
 
89
120
  ### `GamepadDebug`
90
121
 
91
- Drop-in component to visualize inputs. Accepts the same `enabled` and `axisThreshold` props as the hook/bridge.
122
+ - `enabled?: boolean` defaults to `true`.
123
+ - `axisThreshold?: number` — defaults to `0.15`.
124
+
125
+ ## Events and types
126
+
127
+ - `ButtonEvent`: `{ type: 'button'; button: GamepadButtonName; index: number; pressed: boolean; value: number }`
128
+ - `AxisEvent`: `{ type: 'axis'; axis: StickAxisName; index: number; value: number }`
129
+ - `DpadEvent`: `{ type: 'dpad'; key: 'up' | 'down' | 'left' | 'right'; pressed: boolean }`
130
+ - `StatusEvent`: `{ type: 'status'; state: 'connected' | 'disconnected' }`
131
+
132
+ Button names map to the standard gamepad layout (`a`, `b`, `x`, `y`, `lb`, `rb`, `lt`, `rt`, `back`, `start`, `ls`, `rs`, `dpadUp`, `dpadDown`, `dpadLeft`, `dpadRight`, `home`). Unknown indices fall back to `button-N`. Axes map to `leftX`, `leftY`, `rightX`, `rightY` with fallbacks `axis-N`.
92
133
 
93
- ### Types
134
+ ## Behavior notes
94
135
 
95
- - `GamepadButtonName`: `a | b | x | y | lb | rb | lt | rt | back | start | ls | rs | dpadUp | dpadDown | dpadLeft | dpadRight | home | button-N`
96
- - `StickAxisName`: `leftX | leftY | rightX | rightY | axis-N`
136
+ - Reads only the first controller (`navigator.getGamepads()[0]`).
137
+ - D-pad events mirror buttons 12–15; they emit separate `dpad` messages in addition to the raw button events.
138
+ - On disconnect, pressed state is cleared and release events are emitted so you do not get stuck buttons.
139
+ - Keep the bridge mounted; remounting clears internal state and can drop transient events.
140
+ - Axis values below the deadzone are coerced to `0`. Adjust `axisThreshold` if you need more sensitivity.
141
+ - LT/RT expose analog values via `buttonValues.lt` and `buttonValues.rt`.
97
142
 
98
- ## Notes
143
+ ## Patterns
99
144
 
100
- - Reads only the first connected pad (`navigator.getGamepads()[0]`).
101
- - D-pad events are emitted from buttons 12–15; sticks pass through as axes with deadzone applied.
102
- - On disconnect, all pressed states are cleared and release events are emitted.
103
- - The WebView must stay mounted; avoid remounting each render to prevent losing state.
145
+ - **Single place to render**: put the bridge near the root (e.g., inside your `App` provider layer) and consume state anywhere via `useGamepad`.
146
+ - **Status-aware UI**: use `onStatus` to disable controls until `connected` and to reset UI on `disconnected`.
147
+ - **Custom deadzone per screen**: pass `axisThreshold` to either the bridge or the hook depending on which you render.
104
148
 
105
149
  ## Development
106
150
 
@@ -109,7 +153,7 @@ npm install
109
153
  npm run build
110
154
  ```
111
155
 
112
- Outputs to `dist/` with type declarations.
156
+ Build outputs to `dist/` with type declarations.
113
157
 
114
158
  ## License
115
159
 
@@ -10,111 +10,163 @@ const react_native_1 = require("react-native");
10
10
  const useGamepad_1 = __importDefault(require("./useGamepad"));
11
11
  const trackedAxes = ["leftX", "leftY", "rightX", "rightY"];
12
12
  const format = (value) => (value !== null && value !== void 0 ? value : 0).toFixed(2);
13
+ function ControllerVisual({ pressed, axis, value }) {
14
+ const level = (name) => {
15
+ const v = value(name);
16
+ return Math.max(v, pressed(name) ? 1 : 0);
17
+ };
18
+ const mix = (a, b, t) => Math.round(a + (b - a) * t);
19
+ const stickColor = (mag) => {
20
+ const clamped = Math.min(1, Math.max(0, mag));
21
+ const r = mix(34, 37, clamped);
22
+ const g = mix(34, 99, clamped);
23
+ const b = mix(34, 235, clamped);
24
+ return `rgb(${r}, ${g}, ${b})`;
25
+ };
26
+ const stickInner = (mag) => {
27
+ const clamped = Math.min(1, Math.max(0, mag));
28
+ const shade = mix(109, 200, clamped);
29
+ return `rgb(${shade}, ${shade}, ${shade})`;
30
+ };
31
+ const leftMag = Math.min(1, Math.hypot(axis("leftX"), axis("leftY")));
32
+ const rightMag = Math.min(1, Math.hypot(axis("rightX"), axis("rightY")));
33
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psWrapper, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psContainer, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psShoulderRow, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
34
+ styles.psShoulder,
35
+ styles.psShoulderLeft,
36
+ level("lb") > 0 && styles.psShoulderActive,
37
+ { opacity: 0.55 + 0.45 * level("lb") },
38
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
39
+ styles.psShoulderText,
40
+ level("lb") > 0 && styles.psShoulderTextActive,
41
+ ], children: "L1" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
42
+ styles.psShoulder,
43
+ styles.psShoulderLeft,
44
+ level("lt") > 0 && styles.psShoulderActive,
45
+ { opacity: 0.55 + 0.45 * level("lt") },
46
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
47
+ styles.psShoulderText,
48
+ level("lt") > 0 && styles.psShoulderTextActive,
49
+ ], children: "L2" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
50
+ styles.psShoulder,
51
+ styles.psShoulderRight,
52
+ level("rb") > 0 && styles.psShoulderActive,
53
+ { opacity: 0.55 + 0.45 * level("rb") },
54
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
55
+ styles.psShoulderText,
56
+ level("rb") > 0 && styles.psShoulderTextActive,
57
+ ], children: "R1" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
58
+ styles.psShoulder,
59
+ styles.psShoulderRight,
60
+ level("rt") > 0 && styles.psShoulderActive,
61
+ { opacity: 0.55 + 0.45 * level("rt") },
62
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
63
+ styles.psShoulderText,
64
+ level("rt") > 0 && styles.psShoulderTextActive,
65
+ ], children: "R2" }) })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psMiddle }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psPaveTactile }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
66
+ styles.psShare,
67
+ pressed("back") && styles.psActiveButton,
68
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
69
+ styles.psOptions,
70
+ pressed("start") && styles.psActiveButton,
71
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psLeftHand, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psLeftPad, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
72
+ styles.psArrowUp,
73
+ pressed("dpadUp") && styles.psActiveButton,
74
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
75
+ styles.psArrowDown,
76
+ pressed("dpadDown") && styles.psActiveButton,
77
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
78
+ styles.psArrowRight,
79
+ pressed("dpadRight") && styles.psActiveButton,
80
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
81
+ styles.psArrowLeft,
82
+ pressed("dpadLeft") && styles.psActiveButton,
83
+ ] })] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psRightHand, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psRightPad, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
84
+ styles.psTriangle,
85
+ pressed("y") && styles.psActiveButton,
86
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
87
+ styles.psTriangleBas,
88
+ pressed("y") && styles.psTriangleLineActive,
89
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
90
+ styles.psTriangleGauche,
91
+ pressed("y") && styles.psTriangleLineActive,
92
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
93
+ styles.psTriangleDroit,
94
+ pressed("y") && styles.psTriangleLineActive,
95
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
96
+ styles.psCarre,
97
+ pressed("x") && styles.psActiveButton,
98
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
99
+ styles.psCarreRose,
100
+ pressed("x") && styles.psPinkActive,
101
+ ] }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
102
+ styles.psRond,
103
+ pressed("b") && styles.psActiveButton,
104
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
105
+ styles.psRondRouge,
106
+ pressed("b") && styles.psRedActive,
107
+ ] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
108
+ styles.psCroix,
109
+ pressed("a") && styles.psActiveButton,
110
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
111
+ styles.psCroixBleue,
112
+ pressed("a") && styles.psCrossLineActive,
113
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
114
+ styles.psCroixBleue2,
115
+ pressed("a") && styles.psCrossLineActive,
116
+ ] })] })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
117
+ styles.psRollLeft,
118
+ pressed("ls") && styles.psStickPressed,
119
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
120
+ styles.psRollIn,
121
+ {
122
+ transform: [
123
+ { translateX: axis("leftX") * 6 },
124
+ { translateY: axis("leftY") * -6 },
125
+ ],
126
+ backgroundColor: stickColor(leftMag),
127
+ },
128
+ pressed("ls") && styles.psStickInnerPressed,
129
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
130
+ styles.psRollInIn,
131
+ { backgroundColor: stickInner(leftMag) },
132
+ pressed("ls") && styles.psStickCenterPressed,
133
+ ] }) }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
134
+ styles.psPsButton,
135
+ pressed("home") && styles.psActiveButton,
136
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
137
+ styles.psRollRight,
138
+ pressed("rs") && styles.psStickPressed,
139
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
140
+ styles.psRollIn,
141
+ {
142
+ transform: [
143
+ { translateX: axis("rightX") * 6 },
144
+ { translateY: axis("rightY") * -6 },
145
+ ],
146
+ backgroundColor: stickColor(rightMag),
147
+ },
148
+ pressed("rs") && styles.psStickInnerPressed,
149
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
150
+ styles.psRollInIn,
151
+ { backgroundColor: stickInner(rightMag) },
152
+ pressed("rs") && styles.psStickCenterPressed,
153
+ ] }) }) })] })] }) }));
154
+ }
13
155
  function GamepadDebug({ enabled = true, axisThreshold = 0.15, }) {
14
- const { bridge, pressedButtons, axes } = (0, useGamepad_1.default)({
156
+ const { bridge, pressedButtons, axes, buttonValues } = (0, useGamepad_1.default)({
15
157
  enabled,
16
158
  axisThreshold,
17
159
  });
18
160
  const pressedList = (0, react_1.useMemo)(() => Array.from(pressedButtons).sort(), [pressedButtons]);
19
161
  const pressed = (key) => pressedButtons.has(key);
20
- const axis = (key) => { var _a; return (_a = axes[key]) !== null && _a !== void 0 ? _a : 0; };
21
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.container, children: (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { contentContainerStyle: styles.scrollContent, showsVerticalScrollIndicator: false, children: [bridge, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.headerRow, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.title, children: "Gamepad Debug" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.tag, children: (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.tagText, children: ["Enabled ", enabled ? "On" : "Off"] }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.body, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.controllerCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "Controller" }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.controller, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.shoulders, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
22
- styles.bumper,
23
- pressed("lb") && styles.active,
24
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "LB" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
25
- styles.bumper,
26
- pressed("rb") && styles.active,
27
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "RB" }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.triggers, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
28
- styles.trigger,
29
- pressed("lt") && styles.active,
30
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "LT" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
31
- styles.trigger,
32
- pressed("rt") && styles.active,
33
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "RT" }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.midRow, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.stickZone, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.stickRing, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
34
- styles.stick,
35
- {
36
- transform: [
37
- {
38
- translateX: axis("leftX") *
39
- 20,
40
- },
41
- {
42
- translateY: axis("leftY") *
43
- -20,
44
- },
45
- ],
46
- },
47
- pressed("ls") && styles.active,
48
- ] }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.smallLabel, children: "LS" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.centerCluster, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
49
- styles.smallKey,
50
- pressed("back") && styles.active,
51
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "Back" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
52
- styles.smallKey,
53
- pressed("start") && styles.active,
54
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "Start" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
55
- styles.smallKey,
56
- pressed("home") && styles.active,
57
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "Home" }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.faceCluster, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.faceRow, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
58
- styles.faceButton,
59
- pressed("y") &&
60
- styles.faceActive,
61
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "Y" }) }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.faceRow, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
62
- styles.faceButton,
63
- pressed("x") &&
64
- styles.faceActive,
65
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "X" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
66
- styles.faceButton,
67
- pressed("b") &&
68
- styles.faceActive,
69
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "B" }) })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.faceRow, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
70
- styles.faceButton,
71
- pressed("a") &&
72
- styles.faceActive,
73
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "A" }) }) })] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.bottomRow, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.dpad, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
74
- styles.dpadKey,
75
- styles.dpadVertical,
76
- styles.dpadUp,
77
- pressed("dpadUp") && styles.active,
78
- ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
79
- styles.dpadKey,
80
- styles.dpadVertical,
81
- styles.dpadDown,
82
- pressed("dpadDown") &&
83
- styles.active,
84
- ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
85
- styles.dpadKey,
86
- styles.dpadHorizontal,
87
- styles.dpadLeft,
88
- pressed("dpadLeft") &&
89
- styles.active,
90
- ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
91
- styles.dpadKey,
92
- styles.dpadHorizontal,
93
- styles.dpadRight,
94
- pressed("dpadRight") &&
95
- styles.active,
96
- ] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.stickZone, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.stickRing, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
97
- styles.stick,
98
- {
99
- transform: [
100
- {
101
- translateX: axis("rightX") *
102
- 20,
103
- },
104
- {
105
- translateY: axis("rightY") *
106
- -20,
107
- },
108
- ],
109
- },
110
- pressed("rs") && styles.active,
111
- ] }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.smallLabel, children: "RS" })] })] })] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.stateCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "State" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Pressed" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badgeRow, children: pressedList.length === 0 ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.muted, children: "None" })) : (pressedList.map((name) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badge, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.badgeText, children: name }) }, name)))) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Axes" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.axesGrid, children: trackedAxes.map((axisName) => ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.axisItem, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisLabel, children: axisName }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisValue, children: format(axes[axisName]) })] }, axisName))) })] })] })] }) }));
162
+ const axisValue = (key) => { var _a; return (_a = axes[key]) !== null && _a !== void 0 ? _a : 0; };
163
+ const buttonValue = (key) => { var _a; return (_a = buttonValues[key]) !== null && _a !== void 0 ? _a : 0; };
164
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.container, children: (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { contentContainerStyle: styles.scrollContent, showsVerticalScrollIndicator: false, children: [bridge, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.body, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.controllerCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "Controller" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.controller, children: (0, jsx_runtime_1.jsx)(ControllerVisual, { pressed: pressed, axis: axisValue, value: buttonValue }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.stateCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "State" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Pressed" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badgeRow, children: pressedList.length === 0 ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.muted, children: "None" })) : (pressedList.map((name) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badge, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.badgeText, children: name }) }, name)))) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Axes" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.axesGrid, children: trackedAxes.map((axisName) => ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.axisItem, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisLabel, children: axisName }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisValue, children: format(axes[axisName]) })] }, axisName))) })] })] })] }) }));
112
165
  }
113
166
  const styles = react_native_1.StyleSheet.create({
114
167
  container: {
115
168
  flex: 1,
116
169
  width: "100%",
117
- padding: 12,
118
170
  backgroundColor: "#f5f7fb",
119
171
  },
120
172
  scrollContent: {
@@ -209,7 +261,6 @@ const styles = react_native_1.StyleSheet.create({
209
261
  flexShrink: 1,
210
262
  flexBasis: "100%",
211
263
  backgroundColor: "#ffffff",
212
- borderRadius: 14,
213
264
  borderWidth: 1,
214
265
  borderColor: "#e2e8f0",
215
266
  padding: 12,
@@ -228,163 +279,471 @@ const styles = react_native_1.StyleSheet.create({
228
279
  },
229
280
  controller: {
230
281
  backgroundColor: "#f8fafc",
231
- borderRadius: 14,
232
282
  borderWidth: 1,
233
283
  borderColor: "#e2e8f0",
234
284
  padding: 12,
235
- gap: 12,
285
+ alignItems: "center",
286
+ justifyContent: "center",
236
287
  },
237
- shoulders: {
288
+ axesGrid: {
238
289
  flexDirection: "row",
239
- justifyContent: "space-between",
290
+ flexWrap: "wrap",
291
+ gap: 8,
292
+ },
293
+ psWrapper: {
294
+ alignItems: "center",
295
+ justifyContent: "center",
240
296
  },
241
- triggers: {
297
+ psShoulderRow: {
298
+ position: "absolute",
299
+ top: 10,
300
+ left: 0,
301
+ right: 0,
242
302
  flexDirection: "row",
243
303
  justifyContent: "space-between",
244
- gap: 8,
304
+ paddingHorizontal: 24,
305
+ zIndex: 25,
245
306
  },
246
- bumper: {
247
- flex: 1,
307
+ psShoulder: {
308
+ minWidth: 62,
248
309
  paddingVertical: 8,
249
- borderRadius: 10,
310
+ paddingHorizontal: 14,
311
+ borderRadius: 999,
250
312
  backgroundColor: "#e2e8f0",
251
313
  borderWidth: 1,
252
314
  borderColor: "#cbd5e1",
253
315
  alignItems: "center",
316
+ shadowColor: "#000",
317
+ shadowOffset: { width: 0, height: 2 },
318
+ shadowOpacity: 0.25,
319
+ shadowRadius: 6,
320
+ elevation: 4,
254
321
  },
255
- trigger: {
256
- flex: 1,
257
- paddingVertical: 8,
258
- borderRadius: 10,
259
- backgroundColor: "#e2e8f0",
260
- borderWidth: 1,
261
- borderColor: "#cbd5e1",
262
- alignItems: "center",
322
+ psShoulderLeft: {
323
+ marginRight: 6,
263
324
  },
264
- label: {
325
+ psShoulderRight: {
326
+ marginLeft: 6,
327
+ },
328
+ psShoulderActive: {
329
+ backgroundColor: "#2563eb",
330
+ borderColor: "#1e3a8a",
331
+ shadowColor: "#2563eb",
332
+ shadowOpacity: 0.5,
333
+ },
334
+ psShoulderText: {
265
335
  color: "#0f172a",
266
- fontSize: 12,
267
336
  fontWeight: "700",
268
337
  },
269
- smallLabel: {
270
- color: "#475569",
271
- fontSize: 11,
272
- marginTop: 4,
273
- textAlign: "center",
338
+ psShoulderTextActive: {
339
+ color: "#e0f2fe",
274
340
  },
275
- midRow: {
276
- flexDirection: "row",
277
- alignItems: "center",
278
- justifyContent: "space-between",
279
- gap: 12,
341
+ psContainer: {
342
+ position: "relative",
343
+ width: 500,
344
+ height: 350,
345
+ },
346
+ psMiddle: {
347
+ width: 395,
348
+ height: 160,
349
+ backgroundColor: "#e0e0e0",
350
+ marginLeft: 27,
351
+ borderRadius: 25,
352
+ position: "absolute",
353
+ zIndex: 0,
354
+ top: 80,
355
+ },
356
+ psPaveTactile: {
357
+ width: 150,
358
+ height: 80,
359
+ backgroundColor: "#333",
360
+ marginLeft: 147,
361
+ borderRadius: 7,
362
+ position: "absolute",
363
+ zIndex: 10,
364
+ top: 80,
365
+ shadowColor: "#000",
366
+ shadowOffset: { width: 0, height: 0 },
367
+ shadowOpacity: 0.5,
368
+ shadowRadius: 10,
369
+ elevation: 5,
280
370
  },
281
- centerCluster: {
371
+ psShare: {
372
+ width: 12,
373
+ height: 25,
374
+ position: "absolute",
375
+ backgroundColor: "#95a5a6",
376
+ marginLeft: 125,
377
+ marginTop: 85,
378
+ borderRadius: 5,
379
+ zIndex: 10,
380
+ shadowColor: "#000",
381
+ shadowOffset: { width: 0, height: 2 },
382
+ shadowOpacity: 0.3,
383
+ shadowRadius: 5,
384
+ elevation: 3,
385
+ },
386
+ psOptions: {
387
+ width: 12,
388
+ height: 25,
389
+ position: "absolute",
390
+ backgroundColor: "#95a5a6",
391
+ marginLeft: 305,
392
+ marginTop: 85,
393
+ borderRadius: 5,
394
+ zIndex: 10,
395
+ shadowColor: "#000",
396
+ shadowOffset: { width: 0, height: 2 },
397
+ shadowOpacity: 0.3,
398
+ shadowRadius: 5,
399
+ elevation: 3,
400
+ },
401
+ psLeftHand: {
402
+ width: 120,
403
+ height: 260,
404
+ backgroundColor: "#e0e0e0",
405
+ position: "absolute",
406
+ left: 0,
407
+ top: 95,
408
+ transform: [{ rotate: "11deg" }],
409
+ borderTopLeftRadius: 30,
410
+ borderTopRightRadius: 50,
411
+ borderBottomRightRadius: 50,
412
+ borderBottomLeftRadius: 50,
413
+ shadowColor: "#000",
414
+ shadowOffset: { width: -5, height: 5 },
415
+ shadowOpacity: 0.4,
416
+ shadowRadius: 15,
417
+ elevation: 10,
418
+ zIndex: 1,
419
+ paddingTop: 5,
420
+ paddingLeft: 5,
421
+ },
422
+ psRightHand: {
423
+ width: 120,
424
+ height: 260,
425
+ backgroundColor: "#e0e0e0",
426
+ position: "absolute",
427
+ left: 330,
428
+ top: 95,
429
+ transform: [{ rotate: "-11deg" }],
430
+ borderTopLeftRadius: 30,
431
+ borderTopRightRadius: 30,
432
+ borderBottomRightRadius: 50,
433
+ borderBottomLeftRadius: 50,
434
+ shadowColor: "#000",
435
+ shadowOffset: { width: 5, height: 5 },
436
+ shadowOpacity: 0.4,
437
+ shadowRadius: 15,
438
+ elevation: 10,
439
+ zIndex: 1,
440
+ paddingTop: 2,
282
441
  alignItems: "center",
283
- gap: 6,
284
442
  },
285
- smallKey: {
286
- paddingVertical: 6,
287
- paddingHorizontal: 10,
288
- borderRadius: 8,
289
- backgroundColor: "#e2e8f0",
443
+ psLeftPad: {
444
+ backgroundColor: "#c0c0c0",
445
+ width: 112,
446
+ height: 112,
447
+ borderRadius: 56,
448
+ marginTop: 5,
449
+ marginLeft: 10,
290
450
  borderWidth: 1,
291
- borderColor: "#cbd5e1",
451
+ borderColor: "#b0b0b0",
452
+ position: "relative",
292
453
  },
293
- faceCluster: {
294
- alignItems: "center",
295
- gap: 4,
454
+ psArrowUp: {
455
+ width: 22,
456
+ height: 24,
457
+ backgroundColor: "#333",
458
+ position: "absolute",
459
+ top: 18,
460
+ left: 40,
461
+ transform: [{ rotate: "-11deg" }],
462
+ shadowColor: "#000",
463
+ shadowOffset: { width: 0, height: 4 },
464
+ shadowOpacity: 0.5,
465
+ shadowRadius: 5,
466
+ elevation: 5,
296
467
  },
297
- faceRow: {
298
- flexDirection: "row",
299
- gap: 8,
300
- justifyContent: "center",
468
+ psArrowDown: {
469
+ width: 22,
470
+ height: 24,
471
+ backgroundColor: "#333",
472
+ position: "absolute",
473
+ top: 70,
474
+ left: 50,
475
+ transform: [{ rotate: "-11deg" }],
476
+ shadowColor: "#000",
477
+ shadowOffset: { width: 0, height: 4 },
478
+ shadowOpacity: 0.5,
479
+ shadowRadius: 5,
480
+ elevation: 5,
301
481
  },
302
- faceButton: {
303
- width: 42,
304
- height: 42,
305
- borderRadius: 21,
306
- backgroundColor: "#e0f2fe",
482
+ psArrowRight: {
483
+ width: 24,
484
+ height: 22,
485
+ backgroundColor: "#333",
486
+ position: "absolute",
487
+ top: 40,
488
+ left: 72,
489
+ transform: [{ rotate: "-11deg" }],
490
+ shadowColor: "#000",
491
+ shadowOffset: { width: 0, height: 4 },
492
+ shadowOpacity: 0.5,
493
+ shadowRadius: 5,
494
+ elevation: 5,
495
+ },
496
+ psArrowLeft: {
497
+ width: 24,
498
+ height: 22,
499
+ backgroundColor: "#333",
500
+ position: "absolute",
501
+ top: 50,
502
+ left: 17,
503
+ transform: [{ rotate: "-11deg" }],
504
+ shadowColor: "#000",
505
+ shadowOffset: { width: 0, height: 4 },
506
+ shadowOpacity: 0.5,
507
+ shadowRadius: 5,
508
+ elevation: 5,
509
+ },
510
+ psRightPad: {
511
+ backgroundColor: "#c0c0c0",
512
+ width: 112,
513
+ height: 112,
514
+ borderRadius: 56,
515
+ marginTop: 2,
307
516
  borderWidth: 1,
308
- borderColor: "#bae6fd",
517
+ borderColor: "#b0b0b0",
518
+ position: "relative",
519
+ },
520
+ psTriangle: {
521
+ width: 30,
522
+ height: 30,
523
+ position: "absolute",
524
+ borderRadius: 15,
525
+ marginTop: 7,
526
+ marginLeft: 49,
527
+ backgroundColor: "#e0e0e0",
528
+ shadowColor: "#000",
529
+ shadowOffset: { width: 2, height: 2 },
530
+ shadowOpacity: 0.3,
531
+ shadowRadius: 5,
532
+ elevation: 3,
533
+ },
534
+ psTriangleBas: {
535
+ width: 22,
536
+ height: 22,
537
+ position: "absolute",
538
+ marginTop: 27,
539
+ marginLeft: 50,
540
+ transform: [{ rotate: "11deg" }],
541
+ borderTopWidth: 3,
542
+ borderTopColor: "#00c081",
543
+ },
544
+ psTriangleDroit: {
545
+ width: 20,
546
+ height: 20,
547
+ position: "absolute",
548
+ marginTop: 14,
549
+ marginLeft: 51,
550
+ transform: [{ rotate: "67deg" }],
551
+ borderTopWidth: 3,
552
+ borderTopColor: "#00c081",
553
+ },
554
+ psTriangleGauche: {
555
+ width: 20,
556
+ height: 20,
557
+ position: "absolute",
558
+ marginTop: 15,
559
+ marginLeft: 56,
560
+ transform: [{ rotate: "-46deg" }],
561
+ borderTopWidth: 3,
562
+ borderTopColor: "#00c081",
563
+ },
564
+ psCroix: {
565
+ width: 30,
566
+ height: 30,
567
+ position: "absolute",
568
+ borderRadius: 15,
569
+ marginTop: 70,
570
+ marginLeft: 36,
571
+ backgroundColor: "#e0e0e0",
572
+ shadowColor: "#000",
573
+ shadowOffset: { width: 2, height: 2 },
574
+ shadowOpacity: 0.3,
575
+ shadowRadius: 5,
576
+ elevation: 3,
577
+ },
578
+ psCroixBleue: {
579
+ width: 24,
580
+ height: 24,
581
+ transform: [{ rotate: "53deg" }],
582
+ borderRightWidth: 3,
583
+ borderRightColor: "#0a86e5",
584
+ marginTop: -7,
585
+ marginLeft: -4,
586
+ },
587
+ psCroixBleue2: {
588
+ width: 24,
589
+ height: 24,
590
+ transform: [{ rotate: "-32deg" }],
591
+ borderRightWidth: 3,
592
+ borderRightColor: "#0a86e5",
593
+ marginTop: -9,
594
+ marginLeft: -6,
595
+ },
596
+ psCarre: {
597
+ width: 30,
598
+ height: 30,
599
+ position: "absolute",
600
+ borderRadius: 15,
601
+ marginTop: 32,
602
+ marginLeft: 10,
603
+ backgroundColor: "#e0e0e0",
604
+ shadowColor: "#000",
605
+ shadowOffset: { width: 2, height: 2 },
606
+ shadowOpacity: 0.3,
607
+ shadowRadius: 5,
608
+ elevation: 3,
609
+ },
610
+ psCarreRose: {
611
+ width: 17,
612
+ height: 17,
613
+ marginTop: 6,
614
+ marginLeft: 6,
615
+ transform: [{ rotate: "11deg" }],
616
+ borderWidth: 3,
617
+ borderColor: "#e95ce9",
618
+ },
619
+ psRond: {
620
+ width: 30,
621
+ height: 30,
622
+ position: "absolute",
623
+ borderRadius: 15,
624
+ marginTop: 44,
625
+ marginLeft: 74,
626
+ backgroundColor: "#e0e0e0",
627
+ shadowColor: "#000",
628
+ shadowOffset: { width: 2, height: 2 },
629
+ shadowOpacity: 0.3,
630
+ shadowRadius: 5,
631
+ elevation: 3,
632
+ },
633
+ psRondRouge: {
634
+ width: 16,
635
+ height: 16,
636
+ borderRadius: 8,
637
+ transform: [{ rotate: "11deg" }],
638
+ borderWidth: 3,
639
+ borderColor: "#ff3746",
640
+ marginTop: 7,
641
+ marginLeft: 7,
642
+ },
643
+ psRollLeft: {
644
+ width: 60,
645
+ height: 60,
646
+ backgroundColor: "#333",
647
+ position: "absolute",
648
+ borderRadius: 40,
649
+ left: -180,
650
+ top: 40,
651
+ zIndex: 5,
652
+ shadowColor: "#000",
653
+ shadowOffset: { width: 0, height: 5 },
654
+ shadowOpacity: 0.5,
655
+ shadowRadius: 10,
656
+ elevation: 6,
309
657
  alignItems: "center",
310
658
  justifyContent: "center",
311
659
  },
312
- faceText: {
313
- color: "#0f172a",
314
- fontWeight: "800",
315
- },
316
- faceActive: {
317
- backgroundColor: "#2563eb",
318
- borderColor: "#1d4ed8",
319
- shadowColor: "#2563eb",
320
- shadowOpacity: 0.6,
321
- shadowRadius: 6,
322
- },
323
- stickZone: {
324
- width: 96,
660
+ psRollRight: {
661
+ width: 60,
662
+ height: 60,
663
+ backgroundColor: "#333",
664
+ position: "absolute",
665
+ borderRadius: 40,
666
+ left: -73,
667
+ top: 62,
668
+ zIndex: 5,
669
+ shadowColor: "#000",
670
+ shadowOffset: { width: 0, height: 5 },
671
+ shadowOpacity: 0.5,
672
+ shadowRadius: 10,
673
+ elevation: 6,
325
674
  alignItems: "center",
326
- gap: 6,
675
+ justifyContent: "center",
327
676
  },
328
- stickRing: {
329
- width: 84,
330
- height: 84,
331
- borderRadius: 42,
677
+ psRollIn: {
678
+ width: 40,
679
+ height: 40,
680
+ backgroundColor: "#222",
681
+ position: "absolute",
682
+ borderRadius: 30,
683
+ left: 10,
684
+ top: 10,
332
685
  borderWidth: 1,
333
- borderColor: "#cbd5e1",
334
- backgroundColor: "#f8fafc",
686
+ borderColor: "#000",
335
687
  alignItems: "center",
336
688
  justifyContent: "center",
337
689
  },
338
- stick: {
339
- width: 38,
340
- height: 38,
341
- borderRadius: 19,
342
- backgroundColor: "#e2e8f0",
343
- borderWidth: 2,
344
- borderColor: "#cbd5e1",
690
+ psRollInIn: {
691
+ width: 22,
692
+ height: 22,
693
+ backgroundColor: "#6d6d6d",
694
+ position: "absolute",
695
+ borderRadius: 22.5,
696
+ left: 8,
697
+ top: 8,
698
+ },
699
+ psPsButton: {
700
+ width: 23,
701
+ height: 23,
702
+ backgroundColor: "#333",
703
+ position: "absolute",
704
+ borderRadius: 11.5,
705
+ left: -110,
706
+ top: 85,
707
+ zIndex: 10,
708
+ borderWidth: 1,
709
+ borderColor: "#555",
710
+ shadowColor: "#000",
711
+ shadowOffset: { width: 0, height: 0 },
712
+ shadowOpacity: 0.5,
713
+ shadowRadius: 7,
714
+ elevation: 4,
345
715
  },
346
- bottomRow: {
347
- flexDirection: "row",
348
- justifyContent: "space-between",
349
- alignItems: "center",
350
- gap: 12,
716
+ psActiveButton: {
717
+ backgroundColor: "#2563eb",
718
+ borderColor: "#1e3a8a",
719
+ shadowColor: "#2563eb",
720
+ shadowOpacity: 0.4,
721
+ shadowRadius: 6,
351
722
  },
352
- dpad: {
353
- width: 120,
354
- height: 120,
355
- alignItems: "center",
356
- justifyContent: "center",
357
- position: "relative",
723
+ psTriangleLineActive: {
724
+ borderTopColor: "#00f5a8",
358
725
  },
359
- dpadKey: {
360
- position: "absolute",
361
- backgroundColor: "#e2e8f0",
362
- borderColor: "#cbd5e1",
363
- borderWidth: 1,
364
- borderRadius: 6,
726
+ psPinkActive: {
727
+ borderColor: "#d946ef",
365
728
  },
366
- dpadVertical: {
367
- width: 28,
368
- height: 46,
729
+ psRedActive: {
730
+ borderColor: "#fb7185",
369
731
  },
370
- dpadHorizontal: {
371
- width: 46,
372
- height: 28,
732
+ psCrossLineActive: {
733
+ borderRightColor: "#2563eb",
373
734
  },
374
- active: {
375
- borderColor: "#2563eb",
376
- backgroundColor: "#2563eb",
735
+ psStickPressed: {
736
+ shadowOpacity: 0.65,
737
+ shadowRadius: 12,
738
+ },
739
+ psStickInnerPressed: {
740
+ borderColor: "#1e3a8a",
377
741
  shadowColor: "#2563eb",
378
- shadowOpacity: 0.5,
379
- shadowRadius: 4,
742
+ shadowOpacity: 0.45,
743
+ shadowRadius: 10,
380
744
  },
381
- dpadUp: { top: 6, left: 46 },
382
- dpadDown: { bottom: 6, left: 46 },
383
- dpadLeft: { left: 6, top: 46 },
384
- dpadRight: { right: 6, top: 46 },
385
- axesGrid: {
386
- flexDirection: "row",
387
- flexWrap: "wrap",
388
- gap: 8,
745
+ psStickCenterPressed: {
746
+ borderColor: "#93c5fd",
747
+ backgroundColor: "#dbeafe",
389
748
  },
390
749
  });
@@ -1,5 +1,5 @@
1
- import { type JSX } from 'react';
2
- import type { AxisEvent, ButtonEvent, DpadEvent, GamepadButtonName, StickAxisName, StatusEvent } from './types';
1
+ import { type JSX } from "react";
2
+ import type { AxisEvent, ButtonEvent, DpadEvent, GamepadButtonName, StickAxisName, StatusEvent } from "./types";
3
3
  type Options = {
4
4
  enabled?: boolean;
5
5
  axisThreshold?: number;
@@ -11,6 +11,7 @@ type Options = {
11
11
  type Return = {
12
12
  pressedButtons: Set<GamepadButtonName>;
13
13
  axes: Partial<Record<StickAxisName, number>>;
14
+ buttonValues: Partial<Record<GamepadButtonName, number>>;
14
15
  isPressed: (key: GamepadButtonName) => boolean;
15
16
  bridge: JSX.Element | null;
16
17
  };
@@ -9,6 +9,7 @@ const react_1 = require("react");
9
9
  const GamepadBridge_1 = __importDefault(require("./GamepadBridge"));
10
10
  function useGamepad({ enabled = true, axisThreshold = 0.15, onButton, onAxis, onDpad, onStatus, } = {}) {
11
11
  const [pressedButtons, setPressedButtons] = (0, react_1.useState)(new Set());
12
+ const [buttonValues, setButtonValues] = (0, react_1.useState)({});
12
13
  const pressedRef = (0, react_1.useRef)(new Set());
13
14
  const [axes, setAxes] = (0, react_1.useState)({
14
15
  leftX: 0,
@@ -29,6 +30,10 @@ function useGamepad({ enabled = true, axisThreshold = 0.15, onButton, onAxis, on
29
30
  }
30
31
  pressedRef.current = next;
31
32
  setPressedButtons(next);
33
+ setButtonValues((prev) => ({
34
+ ...prev,
35
+ [event.button]: event.value,
36
+ }));
32
37
  onButton === null || onButton === void 0 ? void 0 : onButton(event);
33
38
  }, [onButton]);
34
39
  const handleAxis = (0, react_1.useCallback)((event) => {
@@ -45,9 +50,10 @@ function useGamepad({ enabled = true, axisThreshold = 0.15, onButton, onAxis, on
45
50
  }
46
51
  if (!enabled) {
47
52
  setAxes({ leftX: 0, leftY: 0, rightX: 0, rightY: 0 });
53
+ setButtonValues({});
48
54
  }
49
55
  }, [enabled]);
50
56
  const bridge = (0, react_1.useMemo)(() => ((0, jsx_runtime_1.jsx)(GamepadBridge_1.default, { enabled: enabled, axisThreshold: axisThreshold, onDpad: handleDpad, onButton: handleButton, onAxis: handleAxis, onStatus: onStatus })), [enabled, axisThreshold, handleAxis, handleButton, handleDpad, onStatus]);
51
57
  const isPressed = (0, react_1.useCallback)((key) => pressedRef.current.has(key), []);
52
- return { pressedButtons, axes, isPressed, bridge };
58
+ return { pressedButtons, axes, buttonValues, isPressed, bridge };
53
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-earl-gamepad",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "React Native gamepad bridge via WebView (buttons, sticks, d-pad, status).",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",