@joydle/ui 0.1.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/dist/controls/ActionButton.d.ts +18 -0
- package/dist/controls/ActionButton.d.ts.map +1 -0
- package/dist/controls/ActionButton.js +108 -0
- package/dist/controls/ActionButton.js.map +1 -0
- package/dist/controls/ActionButtonGroup.d.ts +15 -0
- package/dist/controls/ActionButtonGroup.d.ts.map +1 -0
- package/dist/controls/ActionButtonGroup.js +55 -0
- package/dist/controls/ActionButtonGroup.js.map +1 -0
- package/dist/controls/Joystick.d.ts +9 -0
- package/dist/controls/Joystick.d.ts.map +1 -0
- package/dist/controls/Joystick.js +168 -0
- package/dist/controls/Joystick.js.map +1 -0
- package/dist/controls/SwipeZone.d.ts +10 -0
- package/dist/controls/SwipeZone.d.ts.map +1 -0
- package/dist/controls/SwipeZone.js +84 -0
- package/dist/controls/SwipeZone.js.map +1 -0
- package/dist/controls/TouchDPad.d.ts +9 -0
- package/dist/controls/TouchDPad.d.ts.map +1 -0
- package/dist/controls/TouchDPad.js +90 -0
- package/dist/controls/TouchDPad.js.map +1 -0
- package/dist/controls/index.d.ts +6 -0
- package/dist/controls/index.d.ts.map +1 -0
- package/dist/controls/index.js +9 -0
- package/dist/controls/index.js.map +1 -0
- package/dist/core/GameShell.d.ts +43 -0
- package/dist/core/GameShell.d.ts.map +1 -0
- package/dist/core/GameShell.js +91 -0
- package/dist/core/GameShell.js.map +1 -0
- package/dist/core/GameState.d.ts +35 -0
- package/dist/core/GameState.d.ts.map +1 -0
- package/dist/core/GameState.js +74 -0
- package/dist/core/GameState.js.map +1 -0
- package/dist/core/InputManager.d.ts +39 -0
- package/dist/core/InputManager.d.ts.map +1 -0
- package/dist/core/InputManager.js +149 -0
- package/dist/core/InputManager.js.map +1 -0
- package/dist/core/ScaleManager.d.ts +38 -0
- package/dist/core/ScaleManager.d.ts.map +1 -0
- package/dist/core/ScaleManager.js +106 -0
- package/dist/core/ScaleManager.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/types.d.ts +113 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/useGame.d.ts +22 -0
- package/dist/core/useGame.d.ts.map +1 -0
- package/dist/core/useGame.js +40 -0
- package/dist/core/useGame.js.map +1 -0
- package/dist/core/useGameLoop.d.ts +16 -0
- package/dist/core/useGameLoop.d.ts.map +1 -0
- package/dist/core/useGameLoop.js +35 -0
- package/dist/core/useGameLoop.js.map +1 -0
- package/dist/core/useGameSetup.d.ts +16 -0
- package/dist/core/useGameSetup.d.ts.map +1 -0
- package/dist/core/useGameSetup.js +27 -0
- package/dist/core/useGameSetup.js.map +1 -0
- package/dist/hud/BottomHint.d.ts +17 -0
- package/dist/hud/BottomHint.d.ts.map +1 -0
- package/dist/hud/BottomHint.js +68 -0
- package/dist/hud/BottomHint.js.map +1 -0
- package/dist/hud/ComboLabel.d.ts +18 -0
- package/dist/hud/ComboLabel.d.ts.map +1 -0
- package/dist/hud/ComboLabel.js +125 -0
- package/dist/hud/ComboLabel.js.map +1 -0
- package/dist/hud/HUD.d.ts +18 -0
- package/dist/hud/HUD.d.ts.map +1 -0
- package/dist/hud/HUD.js +72 -0
- package/dist/hud/HUD.js.map +1 -0
- package/dist/hud/Lives.d.ts +21 -0
- package/dist/hud/Lives.d.ts.map +1 -0
- package/dist/hud/Lives.js +92 -0
- package/dist/hud/Lives.js.map +1 -0
- package/dist/hud/Meter.d.ts +24 -0
- package/dist/hud/Meter.d.ts.map +1 -0
- package/dist/hud/Meter.js +133 -0
- package/dist/hud/Meter.js.map +1 -0
- package/dist/hud/MiniMap.d.ts +41 -0
- package/dist/hud/MiniMap.d.ts.map +1 -0
- package/dist/hud/MiniMap.js +103 -0
- package/dist/hud/MiniMap.js.map +1 -0
- package/dist/hud/Score.d.ts +15 -0
- package/dist/hud/Score.d.ts.map +1 -0
- package/dist/hud/Score.js +74 -0
- package/dist/hud/Score.js.map +1 -0
- package/dist/hud/Timer.d.ts +21 -0
- package/dist/hud/Timer.d.ts.map +1 -0
- package/dist/hud/Timer.js +111 -0
- package/dist/hud/Timer.js.map +1 -0
- package/dist/hud/WaveLabel.d.ts +15 -0
- package/dist/hud/WaveLabel.d.ts.map +1 -0
- package/dist/hud/WaveLabel.js +70 -0
- package/dist/hud/WaveLabel.js.map +1 -0
- package/dist/hud/index.d.ts +10 -0
- package/dist/hud/index.d.ts.map +1 -0
- package/dist/hud/index.js +13 -0
- package/dist/hud/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/screens/GameOverScreen.d.ts +36 -0
- package/dist/screens/GameOverScreen.d.ts.map +1 -0
- package/dist/screens/GameOverScreen.js +255 -0
- package/dist/screens/GameOverScreen.js.map +1 -0
- package/dist/screens/LoadingScreen.d.ts +21 -0
- package/dist/screens/LoadingScreen.d.ts.map +1 -0
- package/dist/screens/LoadingScreen.js +129 -0
- package/dist/screens/LoadingScreen.js.map +1 -0
- package/dist/screens/LobbyScreen.d.ts +39 -0
- package/dist/screens/LobbyScreen.d.ts.map +1 -0
- package/dist/screens/LobbyScreen.js +266 -0
- package/dist/screens/LobbyScreen.js.map +1 -0
- package/dist/screens/PauseOverlay.d.ts +29 -0
- package/dist/screens/PauseOverlay.d.ts.map +1 -0
- package/dist/screens/PauseOverlay.js +158 -0
- package/dist/screens/PauseOverlay.js.map +1 -0
- package/dist/screens/ScreenManager.d.ts +26 -0
- package/dist/screens/ScreenManager.d.ts.map +1 -0
- package/dist/screens/ScreenManager.js +61 -0
- package/dist/screens/ScreenManager.js.map +1 -0
- package/dist/screens/TitleScreen.d.ts +30 -0
- package/dist/screens/TitleScreen.d.ts.map +1 -0
- package/dist/screens/TitleScreen.js +263 -0
- package/dist/screens/TitleScreen.js.map +1 -0
- package/dist/screens/index.d.ts +7 -0
- package/dist/screens/index.d.ts.map +1 -0
- package/dist/screens/index.js +10 -0
- package/dist/screens/index.js.map +1 -0
- package/dist/theme/ThemeProvider.d.ts +25 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.js +51 -0
- package/dist/theme/ThemeProvider.js.map +1 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +7 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/palettes.d.ts +10 -0
- package/dist/theme/palettes.d.ts.map +1 -0
- package/dist/theme/palettes.js +173 -0
- package/dist/theme/palettes.js.map +1 -0
- package/dist/theme/theme.d.ts +24 -0
- package/dist/theme/theme.d.ts.map +1 -0
- package/dist/theme/theme.js +29 -0
- package/dist/theme/theme.js.map +1 -0
- package/dist/transitions/ScreenTransition.d.ts +19 -0
- package/dist/transitions/ScreenTransition.d.ts.map +1 -0
- package/dist/transitions/ScreenTransition.js +105 -0
- package/dist/transitions/ScreenTransition.js.map +1 -0
- package/dist/transitions/index.d.ts +3 -0
- package/dist/transitions/index.d.ts.map +1 -0
- package/dist/transitions/index.js +6 -0
- package/dist/transitions/index.js.map +1 -0
- package/dist/transitions/presets.d.ts +21 -0
- package/dist/transitions/presets.d.ts.map +1 -0
- package/dist/transitions/presets.js +37 -0
- package/dist/transitions/presets.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
export type ActionButtonSize = 'small' | 'medium' | 'large';
|
|
3
|
+
export interface ActionButtonProps {
|
|
4
|
+
position: 'bottom-left' | 'bottom-right' | 'fullscreen';
|
|
5
|
+
label: string;
|
|
6
|
+
action?: string;
|
|
7
|
+
size?: ActionButtonSize;
|
|
8
|
+
opacity?: number;
|
|
9
|
+
/**
|
|
10
|
+
* When rendered inside an ActionButtonGroup, positioning is managed by the
|
|
11
|
+
* parent. Setting `_managed` suppresses fixed positioning and the media
|
|
12
|
+
* query class so the group can handle layout.
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
_managed?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function ActionButton({ position, label, action, size, opacity, _managed, }: ActionButtonProps): JSX.Element;
|
|
18
|
+
//# sourceMappingURL=ActionButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActionButton.d.ts","sourceRoot":"","sources":["../../src/controls/ActionButton.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAKlC,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,aAAa,GAAG,cAAc,GAAG,YAAY,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6BD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,MAAiB,EACjB,IAAe,EACf,OAAa,EACb,QAAgB,GACjB,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CA8GjC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// @joydle/ui — Single action button (touch)
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import { useContext, useRef, useCallback } from 'preact/hooks';
|
|
6
|
+
import { GameContext } from '../core/index.js';
|
|
7
|
+
// ---- Size map -------------------------------------------------------------
|
|
8
|
+
const SIZE_MAP = {
|
|
9
|
+
small: 48,
|
|
10
|
+
medium: 64,
|
|
11
|
+
large: 80,
|
|
12
|
+
};
|
|
13
|
+
// ---- Helpers --------------------------------------------------------------
|
|
14
|
+
/** CSS to show only on touch devices. */
|
|
15
|
+
const TOUCH_ONLY = `
|
|
16
|
+
.joydle-action-btn { display: none; }
|
|
17
|
+
@media (pointer: coarse) { .joydle-action-btn { display: flex; } }
|
|
18
|
+
`;
|
|
19
|
+
let styleInjected = false;
|
|
20
|
+
function injectStyle() {
|
|
21
|
+
if (styleInjected)
|
|
22
|
+
return;
|
|
23
|
+
styleInjected = true;
|
|
24
|
+
const el = document.createElement('style');
|
|
25
|
+
el.textContent = TOUCH_ONLY;
|
|
26
|
+
document.head.appendChild(el);
|
|
27
|
+
}
|
|
28
|
+
// ---- Component ------------------------------------------------------------
|
|
29
|
+
export function ActionButton({ position, label, action = 'action', size = 'medium', opacity = 0.6, _managed = false, }) {
|
|
30
|
+
const ctx = useContext(GameContext);
|
|
31
|
+
const inputManager = ctx?.inputManager ?? null;
|
|
32
|
+
const pressedRef = useRef(false);
|
|
33
|
+
injectStyle();
|
|
34
|
+
const px = SIZE_MAP[size];
|
|
35
|
+
const handleTouchStart = useCallback((e) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
pressedRef.current = true;
|
|
38
|
+
if (inputManager)
|
|
39
|
+
inputManager.setAction(action, true);
|
|
40
|
+
// Visual feedback via the element itself.
|
|
41
|
+
const el = e.currentTarget;
|
|
42
|
+
if (el)
|
|
43
|
+
el.style.transform = 'scale(0.9)';
|
|
44
|
+
}, [inputManager, action]);
|
|
45
|
+
const handleTouchEnd = useCallback((e) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
pressedRef.current = false;
|
|
48
|
+
if (inputManager)
|
|
49
|
+
inputManager.setAction(action, false);
|
|
50
|
+
const el = e.currentTarget;
|
|
51
|
+
if (el)
|
|
52
|
+
el.style.transform = 'scale(1)';
|
|
53
|
+
}, [inputManager, action]);
|
|
54
|
+
// --- Fullscreen mode: invisible overlay covering the entire screen --------
|
|
55
|
+
if (position === 'fullscreen') {
|
|
56
|
+
const fullStyle = {
|
|
57
|
+
position: 'fixed',
|
|
58
|
+
top: 0,
|
|
59
|
+
left: 0,
|
|
60
|
+
width: '100%',
|
|
61
|
+
height: '100%',
|
|
62
|
+
zIndex: 999,
|
|
63
|
+
pointerEvents: 'auto',
|
|
64
|
+
touchAction: 'none',
|
|
65
|
+
background: 'transparent',
|
|
66
|
+
// Invisible but touchable.
|
|
67
|
+
opacity: 0,
|
|
68
|
+
};
|
|
69
|
+
return (_jsx("div", { class: "joydle-action-btn", style: fullStyle, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchEnd }));
|
|
70
|
+
}
|
|
71
|
+
// --- Standard circular button ---------------------------------------------
|
|
72
|
+
const isLeft = position === 'bottom-left';
|
|
73
|
+
const baseStyle = {
|
|
74
|
+
width: px,
|
|
75
|
+
height: px,
|
|
76
|
+
borderRadius: '50%',
|
|
77
|
+
background: 'rgba(255,255,255,0.25)',
|
|
78
|
+
border: '2px solid rgba(255,255,255,0.5)',
|
|
79
|
+
display: 'flex',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
pointerEvents: 'auto',
|
|
83
|
+
touchAction: 'none',
|
|
84
|
+
userSelect: 'none',
|
|
85
|
+
WebkitUserSelect: 'none',
|
|
86
|
+
transition: 'transform 0.08s ease',
|
|
87
|
+
color: 'white',
|
|
88
|
+
fontSize: Math.round(px * 0.3),
|
|
89
|
+
fontWeight: 700,
|
|
90
|
+
lineHeight: 1,
|
|
91
|
+
boxSizing: 'border-box',
|
|
92
|
+
};
|
|
93
|
+
// When managed by a group, skip fixed positioning.
|
|
94
|
+
if (!_managed) {
|
|
95
|
+
Object.assign(baseStyle, {
|
|
96
|
+
position: 'fixed',
|
|
97
|
+
bottom: 24,
|
|
98
|
+
[isLeft ? 'left' : 'right']: 24,
|
|
99
|
+
opacity,
|
|
100
|
+
zIndex: 1000,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
baseStyle.opacity = opacity;
|
|
105
|
+
}
|
|
106
|
+
return (_jsx("div", { class: _managed ? undefined : 'joydle-action-btn', style: baseStyle, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchEnd, children: label }));
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=ActionButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActionButton.js","sourceRoot":"","sources":["../../src/controls/ActionButton.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
import { type ActionButtonSize } from './ActionButton.js';
|
|
3
|
+
export interface ActionButtonConfig {
|
|
4
|
+
label: string;
|
|
5
|
+
action: string;
|
|
6
|
+
size?: ActionButtonSize;
|
|
7
|
+
}
|
|
8
|
+
export interface ActionButtonGroupProps {
|
|
9
|
+
position: 'bottom-left' | 'bottom-right';
|
|
10
|
+
buttons: ActionButtonConfig[];
|
|
11
|
+
layout: 'row' | 'column' | 'triangle';
|
|
12
|
+
opacity?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function ActionButtonGroup({ position, buttons, layout, opacity, }: ActionButtonGroupProps): JSX.Element;
|
|
15
|
+
//# sourceMappingURL=ActionButtonGroup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActionButtonGroup.d.ts","sourceRoot":"","sources":["../../src/controls/ActionButtonGroup.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIxE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,aAAa,GAAG,cAAc,CAAC;IACzC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqBD,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,OAAO,EACP,MAAM,EACN,OAAa,GACd,EAAE,sBAAsB,GAAG,GAAG,CAAC,OAAO,CAuFtC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { ActionButton } from './ActionButton.js';
|
|
3
|
+
// ---- Helpers --------------------------------------------------------------
|
|
4
|
+
/** CSS to show only on touch devices. */
|
|
5
|
+
const TOUCH_ONLY = `
|
|
6
|
+
.joydle-action-group { display: none; }
|
|
7
|
+
@media (pointer: coarse) { .joydle-action-group { display: flex; } }
|
|
8
|
+
`;
|
|
9
|
+
let styleInjected = false;
|
|
10
|
+
function injectStyle() {
|
|
11
|
+
if (styleInjected)
|
|
12
|
+
return;
|
|
13
|
+
styleInjected = true;
|
|
14
|
+
const el = document.createElement('style');
|
|
15
|
+
el.textContent = TOUCH_ONLY;
|
|
16
|
+
document.head.appendChild(el);
|
|
17
|
+
}
|
|
18
|
+
// ---- Component ------------------------------------------------------------
|
|
19
|
+
export function ActionButtonGroup({ position, buttons, layout, opacity = 0.6, }) {
|
|
20
|
+
injectStyle();
|
|
21
|
+
const isLeft = position === 'bottom-left';
|
|
22
|
+
const gap = 12;
|
|
23
|
+
// --- Triangle layout ------------------------------------------------------
|
|
24
|
+
// First button is primary (larger), remaining are arranged diagonally below.
|
|
25
|
+
if (layout === 'triangle' && buttons.length >= 1) {
|
|
26
|
+
const containerStyle = {
|
|
27
|
+
position: 'fixed',
|
|
28
|
+
bottom: 24,
|
|
29
|
+
[isLeft ? 'left' : 'right']: 24,
|
|
30
|
+
zIndex: 1000,
|
|
31
|
+
pointerEvents: 'auto',
|
|
32
|
+
display: 'flex',
|
|
33
|
+
flexDirection: 'column',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
gap,
|
|
36
|
+
};
|
|
37
|
+
const primary = buttons[0];
|
|
38
|
+
const secondary = buttons.slice(1);
|
|
39
|
+
return (_jsxs("div", { class: "joydle-action-group", style: containerStyle, children: [secondary.length > 0 && (_jsx("div", { style: { display: 'flex', gap, justifyContent: 'center' }, children: secondary.map((btn) => (_jsx(ActionButton, { position: position, label: btn.label, action: btn.action, size: btn.size ?? 'small', opacity: opacity, _managed: true }, btn.action))) })), _jsx(ActionButton, { position: position, label: primary.label, action: primary.action, size: primary.size ?? 'large', opacity: opacity, _managed: true })] }));
|
|
40
|
+
}
|
|
41
|
+
// --- Row / Column layout --------------------------------------------------
|
|
42
|
+
const containerStyle = {
|
|
43
|
+
position: 'fixed',
|
|
44
|
+
bottom: 24,
|
|
45
|
+
[isLeft ? 'left' : 'right']: 24,
|
|
46
|
+
zIndex: 1000,
|
|
47
|
+
pointerEvents: 'auto',
|
|
48
|
+
display: 'flex',
|
|
49
|
+
flexDirection: layout === 'row' ? 'row' : 'column',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
gap,
|
|
52
|
+
};
|
|
53
|
+
return (_jsx("div", { class: "joydle-action-group", style: containerStyle, children: buttons.map((btn) => (_jsx(ActionButton, { position: position, label: btn.label, action: btn.action, size: btn.size ?? 'medium', opacity: opacity, _managed: true }, btn.action))) }));
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=ActionButtonGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActionButtonGroup.js","sourceRoot":"","sources":["../../src/controls/ActionButtonGroup.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
export interface JoystickProps {
|
|
3
|
+
position: 'bottom-left' | 'bottom-right';
|
|
4
|
+
deadzone?: number;
|
|
5
|
+
size?: number;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function Joystick({ position, deadzone, size, opacity, }: JoystickProps): JSX.Element;
|
|
9
|
+
//# sourceMappingURL=Joystick.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Joystick.d.ts","sourceRoot":"","sources":["../../src/controls/Joystick.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAKlC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,GAAG,cAAc,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqBD,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,QAAe,EACf,IAAU,EACV,OAAa,GACd,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CA6L7B"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// @joydle/ui — Virtual analog joystick
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import { useContext, useRef, useCallback, useEffect } from 'preact/hooks';
|
|
6
|
+
import { GameContext } from '../core/index.js';
|
|
7
|
+
// ---- Helpers --------------------------------------------------------------
|
|
8
|
+
/** CSS to show only on touch devices. */
|
|
9
|
+
const TOUCH_ONLY = `
|
|
10
|
+
.joydle-joystick { display: none; }
|
|
11
|
+
@media (pointer: coarse) { .joydle-joystick { display: block; } }
|
|
12
|
+
`;
|
|
13
|
+
let styleInjected = false;
|
|
14
|
+
function injectStyle() {
|
|
15
|
+
if (styleInjected)
|
|
16
|
+
return;
|
|
17
|
+
styleInjected = true;
|
|
18
|
+
const el = document.createElement('style');
|
|
19
|
+
el.textContent = TOUCH_ONLY;
|
|
20
|
+
document.head.appendChild(el);
|
|
21
|
+
}
|
|
22
|
+
// ---- Component ------------------------------------------------------------
|
|
23
|
+
export function Joystick({ position, deadzone = 0.15, size = 120, opacity = 0.6, }) {
|
|
24
|
+
const ctx = useContext(GameContext);
|
|
25
|
+
const inputManager = ctx?.inputManager ?? null;
|
|
26
|
+
const outerRef = useRef(null);
|
|
27
|
+
const thumbRef = useRef(null);
|
|
28
|
+
const trackingRef = useRef(null);
|
|
29
|
+
const centerRef = useRef({ x: 0, y: 0 });
|
|
30
|
+
injectStyle();
|
|
31
|
+
const radius = size / 2;
|
|
32
|
+
const thumbSize = Math.round(size * 0.4);
|
|
33
|
+
const resetThumb = useCallback(() => {
|
|
34
|
+
if (thumbRef.current) {
|
|
35
|
+
thumbRef.current.style.transform = 'translate(-50%, -50%)';
|
|
36
|
+
thumbRef.current.style.left = '50%';
|
|
37
|
+
thumbRef.current.style.top = '50%';
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
const releaseAll = useCallback(() => {
|
|
41
|
+
if (!inputManager)
|
|
42
|
+
return;
|
|
43
|
+
inputManager.setAction('left', false);
|
|
44
|
+
inputManager.setAction('right', false);
|
|
45
|
+
inputManager.setAction('up', false);
|
|
46
|
+
inputManager.setAction('down', false);
|
|
47
|
+
}, [inputManager]);
|
|
48
|
+
const updateFromDelta = useCallback((dx, dy) => {
|
|
49
|
+
if (!inputManager)
|
|
50
|
+
return;
|
|
51
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
52
|
+
const magnitude = Math.min(dist / radius, 1);
|
|
53
|
+
// Clamp thumb position to outer ring boundary.
|
|
54
|
+
const clampedDist = Math.min(dist, radius);
|
|
55
|
+
const angle = Math.atan2(dy, dx);
|
|
56
|
+
const clampedX = Math.cos(angle) * clampedDist;
|
|
57
|
+
const clampedY = Math.sin(angle) * clampedDist;
|
|
58
|
+
if (thumbRef.current) {
|
|
59
|
+
thumbRef.current.style.left = `${radius + clampedX}px`;
|
|
60
|
+
thumbRef.current.style.top = `${radius + clampedY}px`;
|
|
61
|
+
thumbRef.current.style.transform = 'translate(-50%, -50%)';
|
|
62
|
+
}
|
|
63
|
+
// Inside deadzone: release all.
|
|
64
|
+
if (magnitude < deadzone) {
|
|
65
|
+
releaseAll();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Map angle to discrete directions.
|
|
69
|
+
// angle 0 = right, PI/2 = down, PI = left, -PI/2 = up
|
|
70
|
+
const nx = Math.cos(angle);
|
|
71
|
+
const ny = Math.sin(angle);
|
|
72
|
+
// Threshold for activating a direction (cos 45deg ~ 0.707).
|
|
73
|
+
const threshold = 0.383; // cos(67.5deg) — generous diagonal zone
|
|
74
|
+
inputManager.setAction('right', nx > threshold);
|
|
75
|
+
inputManager.setAction('left', nx < -threshold);
|
|
76
|
+
inputManager.setAction('down', ny > threshold);
|
|
77
|
+
inputManager.setAction('up', ny < -threshold);
|
|
78
|
+
}, [inputManager, radius, deadzone, releaseAll]);
|
|
79
|
+
const handleTouchStart = useCallback((e) => {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
if (trackingRef.current !== null)
|
|
82
|
+
return; // already tracking
|
|
83
|
+
const touch = e.changedTouches[0];
|
|
84
|
+
if (!touch)
|
|
85
|
+
return;
|
|
86
|
+
const outer = outerRef.current;
|
|
87
|
+
if (!outer)
|
|
88
|
+
return;
|
|
89
|
+
const rect = outer.getBoundingClientRect();
|
|
90
|
+
centerRef.current = {
|
|
91
|
+
x: rect.left + rect.width / 2,
|
|
92
|
+
y: rect.top + rect.height / 2,
|
|
93
|
+
};
|
|
94
|
+
trackingRef.current = touch.identifier;
|
|
95
|
+
const dx = touch.clientX - centerRef.current.x;
|
|
96
|
+
const dy = touch.clientY - centerRef.current.y;
|
|
97
|
+
updateFromDelta(dx, dy);
|
|
98
|
+
}, [updateFromDelta]);
|
|
99
|
+
const handleTouchMove = useCallback((e) => {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
if (trackingRef.current === null)
|
|
102
|
+
return;
|
|
103
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
104
|
+
const touch = e.changedTouches[i];
|
|
105
|
+
if (touch.identifier === trackingRef.current) {
|
|
106
|
+
const dx = touch.clientX - centerRef.current.x;
|
|
107
|
+
const dy = touch.clientY - centerRef.current.y;
|
|
108
|
+
updateFromDelta(dx, dy);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}, [updateFromDelta]);
|
|
113
|
+
const handleTouchEnd = useCallback((e) => {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
if (trackingRef.current === null)
|
|
116
|
+
return;
|
|
117
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
118
|
+
if (e.changedTouches[i].identifier === trackingRef.current) {
|
|
119
|
+
trackingRef.current = null;
|
|
120
|
+
resetThumb();
|
|
121
|
+
releaseAll();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [resetThumb, releaseAll]);
|
|
126
|
+
// Cleanup on unmount.
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
return () => {
|
|
129
|
+
releaseAll();
|
|
130
|
+
};
|
|
131
|
+
}, [releaseAll]);
|
|
132
|
+
const isLeft = position === 'bottom-left';
|
|
133
|
+
const containerStyle = {
|
|
134
|
+
position: 'fixed',
|
|
135
|
+
bottom: 24,
|
|
136
|
+
[isLeft ? 'left' : 'right']: 24,
|
|
137
|
+
width: size,
|
|
138
|
+
height: size,
|
|
139
|
+
opacity,
|
|
140
|
+
pointerEvents: 'auto',
|
|
141
|
+
zIndex: 1000,
|
|
142
|
+
touchAction: 'none',
|
|
143
|
+
};
|
|
144
|
+
const outerStyle = {
|
|
145
|
+
position: 'relative',
|
|
146
|
+
width: size,
|
|
147
|
+
height: size,
|
|
148
|
+
borderRadius: '50%',
|
|
149
|
+
background: 'rgba(255,255,255,0.15)',
|
|
150
|
+
border: '2px solid rgba(255,255,255,0.4)',
|
|
151
|
+
boxSizing: 'border-box',
|
|
152
|
+
};
|
|
153
|
+
const thumbStyle = {
|
|
154
|
+
position: 'absolute',
|
|
155
|
+
width: thumbSize,
|
|
156
|
+
height: thumbSize,
|
|
157
|
+
borderRadius: '50%',
|
|
158
|
+
background: 'rgba(255,255,255,0.5)',
|
|
159
|
+
border: '2px solid rgba(255,255,255,0.8)',
|
|
160
|
+
left: '50%',
|
|
161
|
+
top: '50%',
|
|
162
|
+
transform: 'translate(-50%, -50%)',
|
|
163
|
+
pointerEvents: 'none',
|
|
164
|
+
boxSizing: 'border-box',
|
|
165
|
+
};
|
|
166
|
+
return (_jsx("div", { class: "joydle-joystick", style: containerStyle, children: _jsx("div", { ref: outerRef, style: outerStyle, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchEnd, children: _jsx("div", { ref: thumbRef, style: thumbStyle }) }) }));
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=Joystick.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Joystick.js","sourceRoot":"","sources":["../../src/controls/Joystick.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JSX, ComponentChildren } from 'preact';
|
|
2
|
+
export type SwipeDirection = 'left' | 'right' | 'up' | 'down';
|
|
3
|
+
export interface SwipeZoneProps {
|
|
4
|
+
direction: 'horizontal' | 'vertical' | 'both';
|
|
5
|
+
onSwipe: (direction: SwipeDirection) => void;
|
|
6
|
+
threshold?: number;
|
|
7
|
+
children?: ComponentChildren;
|
|
8
|
+
}
|
|
9
|
+
export declare function SwipeZone({ direction, onSwipe, threshold, children, }: SwipeZoneProps): JSX.Element;
|
|
10
|
+
//# sourceMappingURL=SwipeZone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SwipeZone.d.ts","sourceRoot":"","sources":["../../src/controls/SwipeZone.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAIrD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;IAC9C,OAAO,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAqBD,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,OAAO,EACP,SAAc,EACd,QAAQ,GACT,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CAoF9B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// @joydle/ui — Swipe detection zone
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import { useRef, useCallback } from 'preact/hooks';
|
|
6
|
+
// ---- Helpers --------------------------------------------------------------
|
|
7
|
+
/** CSS to show only on touch devices. */
|
|
8
|
+
const TOUCH_ONLY = `
|
|
9
|
+
.joydle-swipe-zone { display: none; }
|
|
10
|
+
@media (pointer: coarse) { .joydle-swipe-zone { display: block; } }
|
|
11
|
+
`;
|
|
12
|
+
let styleInjected = false;
|
|
13
|
+
function injectStyle() {
|
|
14
|
+
if (styleInjected)
|
|
15
|
+
return;
|
|
16
|
+
styleInjected = true;
|
|
17
|
+
const el = document.createElement('style');
|
|
18
|
+
el.textContent = TOUCH_ONLY;
|
|
19
|
+
document.head.appendChild(el);
|
|
20
|
+
}
|
|
21
|
+
// ---- Component ------------------------------------------------------------
|
|
22
|
+
export function SwipeZone({ direction, onSwipe, threshold = 30, children, }) {
|
|
23
|
+
injectStyle();
|
|
24
|
+
const startRef = useRef(null);
|
|
25
|
+
const handleTouchStart = useCallback((e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const touch = e.changedTouches[0];
|
|
28
|
+
if (!touch)
|
|
29
|
+
return;
|
|
30
|
+
startRef.current = {
|
|
31
|
+
x: touch.clientX,
|
|
32
|
+
y: touch.clientY,
|
|
33
|
+
id: touch.identifier,
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
const handleTouchEnd = useCallback((e) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
const start = startRef.current;
|
|
39
|
+
if (!start)
|
|
40
|
+
return;
|
|
41
|
+
// Find the matching touch.
|
|
42
|
+
let endTouch = null;
|
|
43
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
44
|
+
if (e.changedTouches[i].identifier === start.id) {
|
|
45
|
+
endTouch = e.changedTouches[i];
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!endTouch)
|
|
50
|
+
return;
|
|
51
|
+
const dx = endTouch.clientX - start.x;
|
|
52
|
+
const dy = endTouch.clientY - start.y;
|
|
53
|
+
const absDx = Math.abs(dx);
|
|
54
|
+
const absDy = Math.abs(dy);
|
|
55
|
+
startRef.current = null;
|
|
56
|
+
// Determine dominant axis and check threshold.
|
|
57
|
+
if (direction === 'horizontal' || direction === 'both') {
|
|
58
|
+
if (absDx >= threshold && absDx >= absDy) {
|
|
59
|
+
onSwipe(dx < 0 ? 'left' : 'right');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (direction === 'vertical' || direction === 'both') {
|
|
64
|
+
if (absDy >= threshold && absDy >= absDx) {
|
|
65
|
+
onSwipe(dy < 0 ? 'up' : 'down');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, [direction, onSwipe, threshold]);
|
|
70
|
+
const style = {
|
|
71
|
+
position: 'absolute',
|
|
72
|
+
top: 0,
|
|
73
|
+
left: 0,
|
|
74
|
+
width: '100%',
|
|
75
|
+
height: '100%',
|
|
76
|
+
pointerEvents: 'auto',
|
|
77
|
+
touchAction: 'none',
|
|
78
|
+
// Invisible.
|
|
79
|
+
background: 'transparent',
|
|
80
|
+
zIndex: 998,
|
|
81
|
+
};
|
|
82
|
+
return (_jsx("div", { class: "joydle-swipe-zone", style: style, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchEnd, children: children }));
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=SwipeZone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SwipeZone.js","sourceRoot":"","sources":["../../src/controls/SwipeZone.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
export interface TouchDPadProps {
|
|
3
|
+
position: 'bottom-left' | 'bottom-right';
|
|
4
|
+
directions: 'horizontal' | 'four-way';
|
|
5
|
+
size?: number;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function TouchDPad({ position, directions, size, opacity, }: TouchDPadProps): JSX.Element;
|
|
9
|
+
//# sourceMappingURL=TouchDPad.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TouchDPad.d.ts","sourceRoot":"","sources":["../../src/controls/TouchDPad.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAKlC,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,aAAa,GAAG,cAAc,CAAC;IACzC,UAAU,EAAE,YAAY,GAAG,UAAU,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8BD,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,UAAU,EACV,IAAU,EACV,OAAa,GACd,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CA8G9B"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// @joydle/ui — Touch D-Pad control (2-dir or 4-dir)
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
import { useContext, useRef, useCallback } from 'preact/hooks';
|
|
6
|
+
import { GameContext } from '../core/index.js';
|
|
7
|
+
const ARROW_PATHS = {
|
|
8
|
+
left: 'M 70 50 L 30 50 L 45 35 M 30 50 L 45 65',
|
|
9
|
+
right: 'M 30 50 L 70 50 L 55 35 M 70 50 L 55 65',
|
|
10
|
+
up: 'M 50 70 L 50 30 L 35 45 M 50 30 L 65 45',
|
|
11
|
+
down: 'M 50 30 L 50 70 L 35 55 M 50 70 L 65 55',
|
|
12
|
+
};
|
|
13
|
+
/** CSS to show only on touch devices. */
|
|
14
|
+
const TOUCH_ONLY = `
|
|
15
|
+
.joydle-touch-dpad { display: none; }
|
|
16
|
+
@media (pointer: coarse) { .joydle-touch-dpad { display: flex; } }
|
|
17
|
+
`;
|
|
18
|
+
let styleInjected = false;
|
|
19
|
+
function injectStyle() {
|
|
20
|
+
if (styleInjected)
|
|
21
|
+
return;
|
|
22
|
+
styleInjected = true;
|
|
23
|
+
const el = document.createElement('style');
|
|
24
|
+
el.textContent = TOUCH_ONLY;
|
|
25
|
+
document.head.appendChild(el);
|
|
26
|
+
}
|
|
27
|
+
// ---- Component ------------------------------------------------------------
|
|
28
|
+
export function TouchDPad({ position, directions, size = 120, opacity = 0.6, }) {
|
|
29
|
+
const ctx = useContext(GameContext);
|
|
30
|
+
const activeRef = useRef(new Set());
|
|
31
|
+
injectStyle();
|
|
32
|
+
const inputManager = ctx?.inputManager ?? null;
|
|
33
|
+
const pressDirection = useCallback((dir, down) => {
|
|
34
|
+
if (!inputManager)
|
|
35
|
+
return;
|
|
36
|
+
if (down) {
|
|
37
|
+
activeRef.current.add(dir);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
activeRef.current.delete(dir);
|
|
41
|
+
}
|
|
42
|
+
inputManager.setAction(dir, down);
|
|
43
|
+
}, [inputManager]);
|
|
44
|
+
const handleTouchStart = useCallback((dir) => (e) => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
pressDirection(dir, true);
|
|
47
|
+
}, [pressDirection]);
|
|
48
|
+
const handleTouchEnd = useCallback((dir) => (e) => {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
pressDirection(dir, false);
|
|
51
|
+
}, [pressDirection]);
|
|
52
|
+
// Button size is proportional to the overall pad size.
|
|
53
|
+
const btnSize = Math.round(size * 0.36);
|
|
54
|
+
const isLeft = position === 'bottom-left';
|
|
55
|
+
const containerStyle = {
|
|
56
|
+
position: 'fixed',
|
|
57
|
+
bottom: 24,
|
|
58
|
+
[isLeft ? 'left' : 'right']: 24,
|
|
59
|
+
width: size,
|
|
60
|
+
height: directions === 'horizontal' ? btnSize : size,
|
|
61
|
+
opacity,
|
|
62
|
+
pointerEvents: 'auto',
|
|
63
|
+
zIndex: 1000,
|
|
64
|
+
};
|
|
65
|
+
const btnBase = {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
width: btnSize,
|
|
68
|
+
height: btnSize,
|
|
69
|
+
borderRadius: '50%',
|
|
70
|
+
background: 'rgba(255,255,255,0.25)',
|
|
71
|
+
border: '2px solid rgba(255,255,255,0.5)',
|
|
72
|
+
display: 'flex',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
justifyContent: 'center',
|
|
75
|
+
touchAction: 'none',
|
|
76
|
+
userSelect: 'none',
|
|
77
|
+
WebkitUserSelect: 'none',
|
|
78
|
+
};
|
|
79
|
+
// Layout positions for buttons within the container.
|
|
80
|
+
const center = Math.round((size - btnSize) / 2);
|
|
81
|
+
const edge = 0;
|
|
82
|
+
const far = size - btnSize;
|
|
83
|
+
const renderBtn = (dir, style) => (_jsx("div", { style: { ...btnBase, ...style }, onTouchStart: handleTouchStart(dir), onTouchEnd: handleTouchEnd(dir), onTouchCancel: handleTouchEnd(dir), children: _jsx("svg", { width: btnSize * 0.6, height: btnSize * 0.6, viewBox: "0 0 100 100", children: _jsx("path", { d: ARROW_PATHS[dir], stroke: "white", "stroke-width": "6", "stroke-linecap": "round", fill: "none" }) }) }));
|
|
84
|
+
if (directions === 'horizontal') {
|
|
85
|
+
return (_jsxs("div", { class: "joydle-touch-dpad", style: containerStyle, children: [renderBtn('left', { left: edge, top: 0 }), renderBtn('right', { right: edge, top: 0 })] }));
|
|
86
|
+
}
|
|
87
|
+
// Four-way layout: up top-center, down bottom-center, left center-left, right center-right.
|
|
88
|
+
return (_jsxs("div", { class: "joydle-touch-dpad", style: containerStyle, children: [renderBtn('up', { left: center, top: edge }), renderBtn('down', { left: center, top: far }), renderBtn('left', { left: edge, top: center }), renderBtn('right', { left: far, top: center })] }));
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=TouchDPad.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TouchDPad.js","sourceRoot":"","sources":["../../src/controls/TouchDPad.tsx"],"names":[],"mappings":"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"}
|