Skip to content

Commit

Permalink
kiosk: support gamepad dpad for navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
eanders-ms committed Oct 9, 2023
1 parent 0fe377e commit bdbb307
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 88 deletions.
52 changes: 26 additions & 26 deletions kiosk/src/Components/GameList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useContext, useCallback } from "react";
import React, { useEffect, useState, useContext, useCallback } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperClass, Pagination } from "swiper";
import "swiper/css";
Expand All @@ -8,11 +8,7 @@ import { playSoundEffect } from "../Services/SoundEffectService";
import { AppStateContext } from "../State/AppStateContext";
import { selectGameByIndex } from "../Transforms/selectGameByIndex";
import { launchGame } from "../Transforms/launchGame";
import {
getHighScores,
getSelectedGameIndex,
getSelectedGameId,
} from "../State";
import { getSelectedGameIndex, getSelectedGameId } from "../State";
import * as GamepadManager from "../Services/GamepadManager";
import * as NavGrid from "../Services/NavGrid";
import { useOnControlPress, useMakeNavigable } from "../Hooks";
Expand All @@ -21,12 +17,18 @@ interface IProps {}

const GameList: React.FC<IProps> = ({}) => {
const { state: kiosk } = useContext(AppStateContext);
const localSwiper = useRef<SwiperClass>();
const [localSwiper, setLocalSwiper] = useState<SwiperClass | undefined>(
undefined
);
const [userInitiatedTransition, setUserInitiatedTransition] =
React.useState(false);
const [pageInited, setPageInited] = React.useState(false);

useMakeNavigable(localSwiper?.current?.el, {
const handleLocalSwiper = (swiper: SwiperClass) => {
setLocalSwiper(swiper);
};

useMakeNavigable(localSwiper?.el, {
exitDirections: [NavGrid.NavDirection.Down, NavGrid.NavDirection.Up],
autofocus: true,
});
Expand All @@ -41,7 +43,8 @@ const GameList: React.FC<IProps> = ({}) => {
};

const syncSelectedGame = useCallback(() => {
const gameIndex = localSwiper?.current?.realIndex || 0;
if (!localSwiper || localSwiper.destroyed) return;
const gameIndex = localSwiper.realIndex || 0;
const selectedGameIndex = getSelectedGameIndex();
if (
selectedGameIndex !== undefined &&
Expand All @@ -53,14 +56,15 @@ const GameList: React.FC<IProps> = ({}) => {

// on page load use effect
useEffect(() => {
if (!localSwiper || localSwiper.destroyed) return;
if (!pageInited) {
if (kiosk.allGames.length) {
if (!kiosk.selectedGameId) {
selectGameByIndex(0);
localSwiper.current?.slideTo(2);
localSwiper.slideTo(2);
} else {
const index = getSelectedGameIndex() || 0;
localSwiper.current?.slideTo(index + 2);
localSwiper.slideTo(index + 2);
}
setPageInited(true);
}
Expand All @@ -71,9 +75,10 @@ const GameList: React.FC<IProps> = ({}) => {
useOnControlPress(
[localSwiper, userInitiatedTransition],
() => {
if (NavGrid.isActiveElement(localSwiper?.current?.el)) {
if (!localSwiper || localSwiper.destroyed) return;
if (NavGrid.isActiveElement(localSwiper.el)) {
setUserInitiatedTransition(true);
setTimeout(() => localSwiper.current?.slideNext(), 1);
setTimeout(() => localSwiper.slideNext(), 1);
}
},
GamepadManager.GamepadControl.DPadRight
Expand All @@ -83,9 +88,10 @@ const GameList: React.FC<IProps> = ({}) => {
useOnControlPress(
[localSwiper, userInitiatedTransition],
() => {
if (NavGrid.isActiveElement(localSwiper?.current?.el)) {
if (!localSwiper || localSwiper.destroyed) return;
if (NavGrid.isActiveElement(localSwiper.el)) {
setUserInitiatedTransition(true);
setTimeout(() => localSwiper.current?.slidePrev(), 1);
setTimeout(() => localSwiper?.slidePrev(), 1);
}
},
GamepadManager.GamepadControl.DPadLeft
Expand All @@ -95,12 +101,12 @@ const GameList: React.FC<IProps> = ({}) => {
useOnControlPress(
[localSwiper, userInitiatedTransition],
() => {
if (NavGrid.isActiveElement(localSwiper?.current?.el)) {
if (!localSwiper || localSwiper.destroyed) return;
if (NavGrid.isActiveElement(localSwiper.el)) {
launchSelectedGame();
}
},
GamepadManager.GamepadControl.AButton,
GamepadManager.GamepadControl.BButton
GamepadManager.GamepadControl.AButton
);

const transitionStart = () => {
Expand Down Expand Up @@ -133,22 +139,16 @@ const GameList: React.FC<IProps> = ({}) => {
slidesPerView={1.8}
centeredSlides={true}
pagination={{ type: "fraction" }}
onSwiper={swiper => {
localSwiper.current = swiper;
}}
onSwiper={handleLocalSwiper}
allowTouchMove={true}
modules={[Pagination]}
onSlideChangeTransitionStart={() => transitionStart()}
onTouchEnd={() => onTouchEnd()}
>
{kiosk.allGames.map((game, index) => {
const gameHighScores = getHighScores(game.id);
return (
<SwiperSlide key={index}>
<GameSlide
highScores={gameHighScores}
game={game}
/>
<GameSlide game={game} />
</SwiperSlide>
);
})}
Expand Down
10 changes: 6 additions & 4 deletions kiosk/src/Components/GameSlide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { GameData, HighScore } from "../Types";
import { AppStateContext } from "../State/AppStateContext";
import HighScoresList from "./HighScoresList";
import { GameMenu } from "./GameMenu";
import { getHighScores } from "../State";

interface IProps {
highScores: HighScore[];
game: GameData;
}
const GameSlide: React.FC<IProps> = ({ highScores, game }) => {
const GameSlide: React.FC<IProps> = ({ game }) => {
const { state: kiosk } = useContext(AppStateContext);

const handleSlideClick = (ev?: React.MouseEvent) => {
Expand All @@ -35,10 +35,12 @@ const GameSlide: React.FC<IProps> = ({ highScores, game }) => {
<div className="gameTitle">{game.name}</div>
<div className="gameDescription">{game.description}</div>
<HighScoresList
highScores={highScores}
highScores={getHighScores(game.id)}
highScoreMode={game.highScoreMode}
/>
{game.date && <div className="gameDate">{lf("Added {0}", game.date)}</div>}
{game.date && (
<div className="gameDate">{lf("Added {0}", game.date)}</div>
)}
</div>

{kiosk.selectedGameId && game.id === kiosk.selectedGameId && (
Expand Down
96 changes: 54 additions & 42 deletions kiosk/src/Services/GamepadManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,36 @@ export enum GamepadControl {
ResetButton = "ResetButton",
}

enum GamepadAxis {
None = "None",
Up = "Up",
Down = "Down",
Left = "Left",
Right = "Right",
}

const gamepadControlToPinIndex: { [key in GamepadControl]: number } = {
[GamepadControl.AButton]: configData.GamepadAButtonPin,
[GamepadControl.BButton]: configData.GamepadBButtonPin,
[GamepadControl.DPadUp]: configData.GamepadUpDownAxis,
[GamepadControl.DPadDown]: configData.GamepadUpDownAxis,
[GamepadControl.DPadLeft]: configData.GamepadLeftRightAxis,
[GamepadControl.DPadRight]: configData.GamepadLeftRightAxis,
[GamepadControl.DPadUp]: configData.GamepadDpadUpButtonPin,
[GamepadControl.DPadDown]: configData.GamepadDpadDownButtonPin,
[GamepadControl.DPadLeft]: configData.GamepadDpadLeftButtonPin,
[GamepadControl.DPadRight]: configData.GamepadDpadRightButtonPin,
[GamepadControl.MenuButton]: configData.GamepadMenuButtonPin,
[GamepadControl.EscapeButton]: configData.GamepadEscapeButtonPin,
[GamepadControl.ResetButton]: configData.GamepadResetButtonPin,
};

const gamepadControlToAxisDirection: { [key in GamepadControl]: number } = {
[GamepadControl.AButton]: 0,
[GamepadControl.BButton]: 0,
[GamepadControl.DPadUp]: -1,
[GamepadControl.DPadDown]: 1,
[GamepadControl.DPadLeft]: -1,
[GamepadControl.DPadRight]: 1,
[GamepadControl.MenuButton]: 0,
[GamepadControl.EscapeButton]: 0,
[GamepadControl.ResetButton]: 0,
const gamepadControlToAxis: { [key in GamepadControl]: GamepadAxis } = {
[GamepadControl.AButton]: GamepadAxis.None,
[GamepadControl.BButton]: GamepadAxis.None,
[GamepadControl.DPadUp]: GamepadAxis.Up,
[GamepadControl.DPadDown]: GamepadAxis.Down,
[GamepadControl.DPadLeft]: GamepadAxis.Left,
[GamepadControl.DPadRight]: GamepadAxis.Right,
[GamepadControl.MenuButton]: GamepadAxis.None,
[GamepadControl.EscapeButton]: GamepadAxis.None,
[GamepadControl.ResetButton]: GamepadAxis.None,
};

export enum ControlValue {
Expand Down Expand Up @@ -129,8 +137,7 @@ class GamepadManager {
);

this.minAxisRequired = Math.max(
configData.GamepadUpDownAxis,
configData.GamepadLeftRightAxis
...Object.values(configData.GamepadAxes).map(v => v.Pin)
);
}

Expand Down Expand Up @@ -258,6 +265,15 @@ class GamepadManager {
}
}

mergeControlValues(...values: ControlValue[]): ControlValue {
for (const value of values) {
if (value === ControlValue.Down) {
return ControlValue.Down;
}
}
return ControlValue.Up;
}

readGamepad(gamepad: Gamepad): ControlStates {
return {
[GamepadControl.AButton]: this.readGamepadButtonValue(
Expand All @@ -268,21 +284,24 @@ class GamepadManager {
gamepad,
GamepadControl.BButton
),
[GamepadControl.DPadUp]: this.readGamepadDirectionValue(
gamepad,
GamepadControl.DPadUp
[GamepadControl.DPadUp]: this.mergeControlValues(
this.readGamepadButtonValue(gamepad, GamepadControl.DPadUp),
this.readGamepadDirectionValue(gamepad, GamepadControl.DPadUp)
),
[GamepadControl.DPadDown]: this.readGamepadDirectionValue(
gamepad,
GamepadControl.DPadDown
[GamepadControl.DPadDown]: this.mergeControlValues(
this.readGamepadButtonValue(gamepad, GamepadControl.DPadDown),
this.readGamepadDirectionValue(gamepad, GamepadControl.DPadDown)
),
[GamepadControl.DPadLeft]: this.readGamepadDirectionValue(
gamepad,
GamepadControl.DPadLeft
[GamepadControl.DPadLeft]: this.mergeControlValues(
this.readGamepadButtonValue(gamepad, GamepadControl.DPadLeft),
this.readGamepadDirectionValue(gamepad, GamepadControl.DPadLeft)
),
[GamepadControl.DPadRight]: this.readGamepadDirectionValue(
gamepad,
GamepadControl.DPadRight
[GamepadControl.DPadRight]: this.mergeControlValues(
this.readGamepadButtonValue(gamepad, GamepadControl.DPadRight),
this.readGamepadDirectionValue(
gamepad,
GamepadControl.DPadRight
)
),
[GamepadControl.MenuButton]: this.readGamepadButtonValue(
gamepad,
Expand Down Expand Up @@ -483,11 +502,8 @@ class GamepadManager {
control: GamepadControl
): ControlValue {
const pinIndex = gamepadControlToPinIndex[control];
if (pinIndex < 0 || pinIndex >= gamepad.buttons.length) {
throw new Error(
`Gamepad at index ${gamepad.index} does not have a button at pin ${pinIndex}`
);
}
if (pinIndex < 0 || pinIndex >= gamepad.buttons.length)
return ControlValue.Up;
return gamepad.buttons[pinIndex].pressed
? ControlValue.Down
: ControlValue.Up;
Expand All @@ -497,15 +513,11 @@ class GamepadManager {
gamepad: Gamepad,
control: GamepadControl
): ControlValue {
const axisIndex = gamepadControlToPinIndex[control];
const threshold =
gamepadControlToAxisDirection[control] *
configData.GamepadLeftRightThreshold;
if (axisIndex < 0 || axisIndex >= gamepad.axes.length) {
throw new Error(
`Gamepad at index ${gamepad.index} does not have an axis at index ${axisIndex}`
);
}
const axisConf = configData.GamepadAxes[gamepadControlToAxis[control]];
const axisIndex = axisConf.Pin;
if (axisIndex < 0 || axisIndex >= gamepad.axes.length)
return ControlValue.Up;
const threshold = axisConf.Sign * axisConf.Threshold;
if (threshold < 0) {
return gamepad.axes[axisIndex] <= threshold
? ControlValue.Down
Expand Down
Loading

0 comments on commit bdbb307

Please sign in to comment.