Skip to content

Commit

Permalink
client: show what controller buttons are being pressed (#19)
Browse files Browse the repository at this point in the history
README: Add another example link.

Client
* Add explanation to enable insecure content.
* show controller and sticks/buttons light up when being used
* keyboard: Show button being pressed.
* Put video in between controllers.
  • Loading branch information
juharris authored Jun 9, 2020
1 parent 97da47d commit 923091e
Show file tree
Hide file tree
Showing 17 changed files with 625 additions and 119 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ You <===> Website <=====> Server <--Bluetooth--> Switch
'------------------------------ Streaming Server
```

Example [video](https://www.youtube.com/watch?v=EIofCEfQA1E).
Example [video](https://youtu.be/EIofCEfQA1E) of someone playing my Switch from another city.

Example [video](https://youtu.be/TJlWK2HU8Do) of me using an Xbox controller (that does not have Bluetooth) to play my Switch.

# Status
One keyboard layout or gaming controller layout is supported to map input to the control sticks and the buttons on a Nintendo Switch controller.
Expand Down
21 changes: 21 additions & 0 deletions website-client/src/components/Button/Button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.Button {
background-color: #111;
color: white;
display: flex;
border-radius: 50%;
height: 4rem;
width: 4rem;
justify-content: center;
align-items: center;
text-transform: capitalize;
user-select: none;
}

.Button h1 {
text-align: center;
}

.Pressed {
background-color: #999;
color: #111;
}
14 changes: 14 additions & 0 deletions website-client/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
import classes from './Button.module.css'

const Button: React.FunctionComponent<any> = (props: any) => {
let classList = classes.Button
if (props.button.pressed) classList += " " + classes.Pressed
return (
<div className={classList}>
<h1>{props.button.symbol}</h1>
</div>
)
}

export default Button
97 changes: 97 additions & 0 deletions website-client/src/components/Controller/Controller.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* TODO Use lower-case for the names. */

.Left {
display: flex;
align-items: center;
}

.Middle {
/* Some of the settings were used before the video was in between the controls. */
/* display: flex; */
/* flex-direction: column; */
/* flex-grow: 1; */
min-width: 23rem;
}

.Right {

}

.LargeButton {
background-color: #111;
margin: 1rem;
border-radius: 10%;
display: flex;
justify-content: center;
}

.LargeButton p {
color: white;
font-weight: 700;
font-size: 30px;
margin: 0;
user-select: none;
}

.SmallButton {
background-color: #111;
margin: 0;
border-radius: 10%;
display: flex;
justify-content: center;
flex-grow: 1;
}

.SmallButton p {
color: white;
font-weight: 700;
font-size: 20px;
margin: 0;
user-select: none;
}

.Row {
display: flex;
padding: 0 1rem 2rem
}

.Symbol {
margin: 0;
}

.Symbol p {
margin: 0 1rem;
color: #111;
font-weight: 900;
font-size: 32px;
user-select: none;
}

.Pressed {
background-color: #999;
color: #111;
}

.PressedAlt p {
color: #999;
}

.capture {
position: relative;
top: -40px;
margin-left: 140px;
}

.capture p {
font-size: 70px;
}

.home {
position: relative;
top: -40px;
margin-left: 40px;
}

.home p {
font-size: 70px;
}
135 changes: 135 additions & 0 deletions website-client/src/components/Controller/Controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { createStyles, withStyles } from '@material-ui/core'
import React from 'react'
import Diamond from '../Diamond/Diamond'
import VideoStream from '../VideoStream'
import cssClasses from './Controller.module.css'
import { ControllerState } from './ControllerState'
import Joystick from './Joystick/Joystick'

const styles = () => createStyles({
controller: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
},
})

class Controller extends React.Component<any> {
render(): React.ReactNode {
const { classes } = this.props
const controllerState: ControllerState = this.props.controllerState || new ControllerState()

return (
<div className={classes.controller}>
<div>
<div>
<div
className={
cssClasses.LargeButton +
(controllerState.l.isPressed ? " " + cssClasses.Pressed : "")
}
>
<p>L</p>
</div>
<div className={cssClasses.Row}>
<div
className={
cssClasses.SmallButton +
(controllerState.zl.isPressed ? " " + cssClasses.Pressed : "")
}
>
<p>ZL</p>
</div>
<div
className={
cssClasses.Symbol +
(controllerState.minus.isPressed ? " " + cssClasses.PressedAlt : "")
}
>
{/* Slightly wider than a typical minus. */}
<p></p>
</div>
</div>
</div>
<Joystick
x={controllerState.leftStick.horizontalValue}
y={controllerState.leftStick.verticalValue}
pressed={controllerState.leftStick.isPressed}
/>
<Diamond
buttons={[
{ symbol: "▶", pressed: controllerState.arrowRight.isPressed },
{ symbol: "▼", pressed: controllerState.arrowDown.isPressed },
{ symbol: "▲", pressed: controllerState.arrowUp.isPressed },
{ symbol: "◀", pressed: controllerState.arrowLeft.isPressed },
]}
/>
<div
className={
cssClasses.Symbol + " " + cssClasses.capture +
(controllerState.capture.isPressed ? " " + cssClasses.PressedAlt : "")
}
>
<p></p>
</div>
</div>
<div className={cssClasses.Middle}>
<VideoStream {...this.props.videoStreamProps} />
</div>
<div>
<div>
<div
className={
cssClasses.LargeButton +
(controllerState.r.isPressed ? " " + cssClasses.Pressed : "")
}
>
<p>R</p>
</div>
<div className={cssClasses.Row}>
<div
className={
cssClasses.Symbol +
(controllerState.plus.isPressed ? " " + cssClasses.PressedAlt : "")
}
>
<p>+</p>
</div>
<div
className={
cssClasses.SmallButton +
(controllerState.zr.isPressed ? " " + cssClasses.Pressed : "")
}
>
<p>ZR</p>
</div>
</div>
</div>
<Diamond
buttons={[
{ symbol: "a", pressed: controllerState.a.isPressed },
{ symbol: "b", pressed: controllerState.b.isPressed },
{ symbol: "x", pressed: controllerState.x.isPressed },
{ symbol: "y", pressed: controllerState.y.isPressed },
]}
/>
<Joystick
x={controllerState.rightStick.horizontalValue}
y={controllerState.rightStick.verticalValue}
pressed={controllerState.rightStick.isPressed}
/>
<div
className={
cssClasses.Symbol + " " + cssClasses.home +
(controllerState.home.isPressed ? " " + cssClasses.PressedAlt : "")
}
>
<p></p>
</div>
</div>
</div>
)
}
}

export default withStyles(styles)(Controller)
42 changes: 42 additions & 0 deletions website-client/src/components/Controller/ControllerState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class ButtonState {
public isPressed = false
}

class StickState extends ButtonState {
/** -1 is left, +1 is right */
public horizontalValue = 0

/** -1 is up, +1 is down */
public verticalValue = 0
}

// Member names in here match actions.
class ControllerState {
// Arrows
arrowLeft = new ButtonState()
arrowRight = new ButtonState()
arrowUp = new ButtonState()
arrowDown = new ButtonState()

a = new ButtonState()
b = new ButtonState()
x = new ButtonState()
y = new ButtonState()

l = new ButtonState()
zl = new ButtonState()

r = new ButtonState()
zr = new ButtonState()

minus = new ButtonState()
plus = new ButtonState()

capture = new ButtonState()
home = new ButtonState()

leftStick = new StickState()
rightStick = new StickState()
}

export { ControllerState, ButtonState }
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.joystickHolder {
height: 6rem;
width: 6rem;
background-color: #111;
border-radius: 50%;
margin: 1rem auto;
}

.joystick {
height: 5rem;
width: 5rem;
background-color: #222;
border-radius: 50%;
margin: 1rem auto;
position: relative;
top: 0.5rem;
}

.pressed {
background-color: #999;
}
24 changes: 24 additions & 0 deletions website-client/src/components/Controller/Joystick/Joystick.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import classes from './Joystick.module.css'

const Joystick: React.FunctionComponent<any> = (props: any) => {
let joystickHolderClassList = classes.joystickHolder
let joystickClassList = classes.joystick
const movedThreshold = 0.15
if (props.pressed) {
joystickHolderClassList += " " + classes.pressed
}
if (Math.abs(props.x) > movedThreshold || Math.abs(props.y) > movedThreshold) {
joystickClassList += " " + classes.pressed
}

const styles = {
transform: `translate(${props.x * 15}px, ${props.y * 15}px)`,
}
return <div className={joystickHolderClassList}>
<div className={joystickClassList} style={styles}>
</div>
</div>
}

export default Joystick
11 changes: 11 additions & 0 deletions website-client/src/components/Diamond/Blank.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'

const Blank: React.FunctionComponent = () => {
const style = {
width: "4rem",
height: "4em",
}
return <div style={style} />
}

export default Blank
6 changes: 6 additions & 0 deletions website-client/src/components/Diamond/Diamond.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.Diamond {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
Loading

0 comments on commit 923091e

Please sign in to comment.