// Copyright (C) 2017-2026 Smart code 203358507 import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useGamepad } from 'stremio/services'; import type { ControllerType } from 'stremio/services/GamepadContext'; import styles from './styles.less'; type ActiveButton = string | null; const CX = 400; const ARROW = { UP: '↑', DOWN: '↓', LEFT: '←', RIGHT: '→' }; type FaceLayout = { top: { glyph: string; fontSize: number; weight: number }; right: { glyph: string; fontSize: number; weight: number }; bottom: { glyph: string; fontSize: number; weight: number }; left: { glyph: string; fontSize: number; weight: number }; lb: string; rb: string; lt: string; rt: string; }; const LAYOUTS: Record = { playstation: { top: { glyph: '△', fontSize: 12, weight: 400 }, right: { glyph: '○', fontSize: 12, weight: 400 }, bottom: { glyph: '✕', fontSize: 12, weight: 400 }, left: { glyph: '□', fontSize: 12, weight: 400 }, lb: 'L1', rb: 'R1', lt: 'L2', rt: 'R2', }, xbox: { top: { glyph: 'Y', fontSize: 11, weight: 700 }, right: { glyph: 'B', fontSize: 11, weight: 700 }, bottom: { glyph: 'A', fontSize: 11, weight: 700 }, left: { glyph: 'X', fontSize: 11, weight: 700 }, lb: 'LB', rb: 'RB', lt: 'LT', rt: 'RT', }, generic: { top: { glyph: '△', fontSize: 12, weight: 400 }, right: { glyph: '○', fontSize: 12, weight: 400 }, bottom: { glyph: '✕', fontSize: 12, weight: 400 }, left: { glyph: '□', fontSize: 12, weight: 400 }, lb: 'L1', rb: 'R1', lt: 'L2', rt: 'R2', }, }; const GamepadDiagram = () => { const { t } = useTranslation(); const gamepad = useGamepad(); const [active, setActive] = useState(null); const layout = LAYOUTS[gamepad?.controllerType ?? 'generic']; useEffect(() => { let timeout: ReturnType; const flash = (button: string) => () => { setActive(button); clearTimeout(timeout); timeout = setTimeout(() => setActive(null), 400); }; gamepad?.on('buttonA', 'gamepad-diagram', flash('bottom')); gamepad?.on('buttonB', 'gamepad-diagram', flash('right')); gamepad?.on('buttonX', 'gamepad-diagram', flash('left')); gamepad?.on('buttonY', 'gamepad-diagram', flash('top')); gamepad?.on('buttonLT', 'gamepad-diagram', flash('lb')); gamepad?.on('buttonRT', 'gamepad-diagram', flash('rb')); gamepad?.on('analog', 'gamepad-diagram', (dir) => dir && flash('stick-' + dir)()); gamepad?.on('analogRight', 'gamepad-diagram', (dir) => dir && flash('rstick-' + dir)()); return () => { clearTimeout(timeout); gamepad?.off('buttonA', 'gamepad-diagram'); gamepad?.off('buttonB', 'gamepad-diagram'); gamepad?.off('buttonX', 'gamepad-diagram'); gamepad?.off('buttonY', 'gamepad-diagram'); gamepad?.off('buttonLT', 'gamepad-diagram'); gamepad?.off('buttonRT', 'gamepad-diagram'); gamepad?.off('analog', 'gamepad-diagram'); gamepad?.off('analogRight', 'gamepad-diagram'); }; }, [gamepad]); const glow = (id: string) => active === id ? '#7b5bf5' : undefined; const glowOp = (id: string) => active === id ? 1 : undefined; const SX = 130; const BX = 120; const STX = 75; const BY = 30; // Xbox controllers are asymmetric — left stick sits upper-left (where the // d-pad is on PlayStation) and the d-pad drops to the lower-left. const isXbox = (gamepad?.controllerType ?? 'generic') === 'xbox'; const lstickPos = isXbox ? { cx: CX - BX, cy: 148 + BY } : { cx: CX - STX, cy: 240 + BY }; const dpadPos = isXbox ? { cx: CX - STX, cy: 240 + BY } : { cx: CX - BX, cy: 149 + BY }; const navLine = isXbox ? { x1: CX - BX - 24, y1: 148 + BY } : { x1: CX - STX - 24, y1: 232 + BY }; return ( {layout.lt} {layout.rt} {layout.lb} {layout.rb} {layout.top.glyph} {layout.right.glyph} {layout.bottom.glyph} {layout.left.glyph} {ARROW.UP} {ARROW.DOWN} {ARROW.LEFT} {ARROW.RIGHT} {t('GAMEPAD_ACTION_PREV_TAB')} {t('GAMEPAD_ACTION_NAVIGATE')} {t('GAMEPAD_ACTION_GUIDE')} {t('GAMEPAD_LABEL_PLAY_PAUSE_PLAYER')} {t('GAMEPAD_ACTION_NEXT_TAB')} {t('GAMEPAD_ACTION_FULLSCREEN')} {t('GAMEPAD_ACTION_BACK')} {t('GAMEPAD_ACTION_SELECT')} {t('GAMEPAD_LABEL_SEEK_VOL')} {t('GAMEPAD_LABEL_COMPAT')} ); }; export default GamepadDiagram;