Skip to content

Commit fe5f0b2

Browse files
committed
Make Gamepad and Touch Controls Mutually Exclusive [AARD-1945] (#1229)
2 parents 7f6410b + 790d121 commit fe5f0b2

File tree

7 files changed

+203
-150
lines changed

7 files changed

+203
-150
lines changed

fission/bun.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"framer-motion": "^10.18.0",
2121
"lygia": "^1.3.3",
2222
"playwright": "^1.54.0",
23-
"postprocessing": "^6.37.4",
23+
"postprocessing": "^6.37.6",
2424
"react": "^18.3.1",
2525
"react-colorful": "^5.6.1",
2626
"react-dom": "^18.3.1",
@@ -1267,7 +1267,7 @@
12671267

12681268
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
12691269

1270-
"postprocessing": ["postprocessing@6.37.4", "", { "peerDependencies": { "three": ">= 0.157.0 < 0.178.0" } }, "sha512-O4dv29MDRSjXgMF1Luz3YHlT7NawKIliCfO2OgUCtIMTLNMCg+v0RLuT9/LQSDtVNXxUHGyy3mucbF3UePcElA=="],
1270+
"postprocessing": ["postprocessing@6.37.6", "", { "peerDependencies": { "three": ">= 0.157.0 < 0.179.0" } }, "sha512-KrdKLf1257RkoIk3z3nhRS0aToKrX2xJgtR0lbnOQUjd+1I4GVNv1gQYsQlfRglvEXjpzrwqOA5fXfoDBimadg=="],
12711271

12721272
"potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="],
12731273

fission/src/test/bootstrap/AppMount.test.tsx

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -56,48 +56,44 @@ describe("React Mounting", async () => {
5656
// importing main.tsx has side effects that I could not clean up and can only be done once (per file),
5757
// so I am using one test and many annotations. It's possible that there's a better way, but I couldn't
5858
// find it in 4 hours of trying
59-
test(
60-
"App fully mounts through main.tsx",
61-
async ({ annotate, skip }) => {
62-
skip(server.browser == "firefox", "WebGL bug in Github Actions on Firefox")
63-
64-
await import("@/main.tsx")
65-
66-
expect(window.convertAuthToken).toBeDefined()
67-
expectTypeOf(window.convertAuthToken).toBeFunction()
68-
expect(window.gtag).toBeDefined()
69-
expectTypeOf(window.gtag).toBeFunction()
70-
await annotate("expected global functions mount")
71-
72-
// assorted style rules from index.css
73-
const style = window.getComputedStyle(document.body)
74-
expect(style.overflow).toBe("hidden")
75-
expect(style.overscrollBehavior).toBe("none")
76-
expect(style.fontFamily.split(",")[0].trim()).toBe("Artifakt")
77-
await annotate("index.css applied correctly")
78-
79-
expect(renderMock).toHaveBeenCalledOnce()
80-
assert(screen != null, "Screen was null")
81-
82-
const screenElement = screen.baseElement
83-
expect(screenElement.querySelector("canvas")).toBeInTheDocument()
84-
expect(screen.getByText("Singleplayer")).toBeInTheDocument()
85-
await annotate("DOM successfully updated to include Synthesis components")
86-
const initWorldSpy = vi.spyOn(World, "initWorld")
87-
await screen.getByText("Singleplayer").click()
88-
expect(initWorldSpy).toHaveBeenCalledOnce()
89-
await annotate("Singleplayer Button calls initWorld")
90-
91-
await wait(50)
92-
93-
await annotate("Initial Scene DOM", { contentType: "text/html", body: document.documentElement.outerHTML })
94-
95-
screen.unmount()
96-
97-
await annotate("Screen unmounted gracefully")
98-
},
99-
{ timeout: 20000 }
100-
)
59+
test("App fully mounts through main.tsx", async ({ annotate, skip }) => {
60+
skip(server.browser == "firefox", "WebGL bug in Github Actions on Firefox")
61+
62+
await import("@/main.tsx")
63+
64+
expect(window.convertAuthToken).toBeDefined()
65+
expectTypeOf(window.convertAuthToken).toBeFunction()
66+
expect(window.gtag).toBeDefined()
67+
expectTypeOf(window.gtag).toBeFunction()
68+
await annotate("expected global functions mount")
69+
70+
// assorted style rules from index.css
71+
const style = window.getComputedStyle(document.body)
72+
expect(style.overflow).toBe("hidden")
73+
expect(style.overscrollBehavior).toBe("none")
74+
expect(style.fontFamily.split(",")[0].trim()).toBe("Artifakt")
75+
await annotate("index.css applied correctly")
76+
77+
expect(renderMock).toHaveBeenCalledOnce()
78+
assert(screen != null, "Screen was null")
79+
80+
const screenElement = screen.baseElement
81+
expect(screenElement.querySelector("canvas")).toBeInTheDocument()
82+
expect(screen.getByText("Singleplayer")).toBeInTheDocument()
83+
await annotate("DOM successfully updated to include Synthesis components")
84+
const initWorldSpy = vi.spyOn(World, "initWorld")
85+
await screen.getByText("Singleplayer").click()
86+
expect(initWorldSpy).toHaveBeenCalledOnce()
87+
await annotate("Singleplayer Button calls initWorld")
88+
89+
await wait(50)
90+
91+
await annotate("Initial Scene DOM", { contentType: "text/html", body: document.documentElement.outerHTML })
92+
93+
screen.unmount()
94+
95+
await annotate("Screen unmounted gracefully")
96+
}, 20000)
10197
})
10298

10399
function wait(milliseconds: number) {

fission/src/ui/components/Checkbox.tsx

Lines changed: 15 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import React, { useState } from "react"
2-
import Label, { LabelSize } from "./Label"
3-
import { Switch } from "@mui/base/Switch"
4-
import { Box } from "@mui/material"
5-
import { LabelWithTooltip } from "./StyledComponents"
6-
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
2+
import StatefulCheckbox from "@/components/StatefulCheckbox.tsx"
73

84
type CheckboxProps = {
95
label: string
106
className?: string
117
defaultState: boolean
12-
stateOverride?: boolean
138
hideLabel?: boolean
149
onClick?: (checked: boolean) => void
1510
tooltipText?: string
@@ -27,57 +22,29 @@ type CheckboxProps = {
2722
* @param {function} props.onClick - Callback function to handle state changes of the checkbox.
2823
* @param {string} props.tooltipText - Text shows as a tooltip next to the label.
2924
*
30-
* @returns {JSX.Element} The rendered Dropdown component.
25+
* @returns {React.ReactElement} The rendered Dropdown component.
3126
*/
3227
const Checkbox: React.FC<CheckboxProps> = ({
3328
label,
3429
className,
3530
defaultState,
36-
stateOverride,
3731
hideLabel,
3832
onClick,
3933
tooltipText,
40-
}: CheckboxProps): JSX.Element => {
41-
const [state] = useState(defaultState)
34+
}: CheckboxProps): React.ReactElement => {
35+
const [state, setState] = useState(defaultState)
4236
return (
43-
<Box
44-
display="flex"
45-
flexDirection={"row"}
46-
justifyContent={"space-between"}
47-
alignItems={"center"}
48-
textAlign={"center"}
49-
>
50-
{hideLabel ? null : tooltipText ? (
51-
LabelWithTooltip(label, tooltipText)
52-
) : (
53-
<Label size={LabelSize.SMALL} className={`mr-12 ${className} whitespace-nowrap`}>
54-
{label}
55-
</Label>
56-
)}
57-
<Switch
58-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onClick && onClick(e.target.checked)}
59-
{...SoundPlayer.checkboxSoundEffects()}
60-
slotProps={{
61-
root: {
62-
className: `group relative inline-block w-[24px] h-[24px] m-2.5 cursor-pointer transform transition-transform hover:scale-[1.03] active:scale-[1.06]`,
63-
},
64-
input: {
65-
className: `cursor-inherit absolute w-full h-full top-0 left-0 opacity-0 z-10 border-none`,
66-
},
67-
track: ownerState => {
68-
return {
69-
className: `absolute block w-full h-full transition rounded-full border border-solid outline-none border-interactive-element-right dark:border-interactive-element-right group-[.base--focusVisible]:shadow-outline-switch ${ownerState.checked ? "bg-gradient-to-br from-interactive-element-left to-interactive-element-right" : "bg-background-secondary"} transform transition-transform group-hover:scale-[1.03] group-active:scale-[1.06]`,
70-
}
71-
},
72-
thumb: {
73-
className: `display-none`,
74-
},
75-
}}
76-
defaultChecked={stateOverride != null ? undefined : state}
77-
// checked={state}
78-
id="checkbox-switch"
79-
/>
80-
</Box>
37+
<StatefulCheckbox
38+
label={label}
39+
checked={state}
40+
className={className}
41+
hideLabel={hideLabel}
42+
tooltipText={tooltipText}
43+
onClick={v => {
44+
setState(v)
45+
onClick?.(v)
46+
}}
47+
/>
8148
)
8249
}
8350

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from "react"
2+
import Label, { LabelSize } from "./Label"
3+
import { Switch } from "@mui/base/Switch"
4+
import { Box } from "@mui/material"
5+
import { LabelWithTooltip } from "./StyledComponents"
6+
import { SoundPlayer } from "@/systems/sound/SoundPlayer"
7+
8+
type CheckboxProps = {
9+
/**
10+
* The label text that will be on the right of the checkbox.
11+
*/
12+
label: string
13+
/**
14+
* Custom styling options.
15+
*/
16+
className?: string
17+
/**
18+
* A variable that controls the current state of the checkbox
19+
*/
20+
checked: boolean
21+
22+
/**
23+
* If true, the checkbox will not be labeled.
24+
*/
25+
hideLabel?: boolean
26+
/**
27+
* Callback function to handle state changes of the checkbox.
28+
* @param checked new state of the checkbox
29+
*/
30+
onClick?: (checked: boolean) => void
31+
/**
32+
* Text to show as a tooltip next to the label.
33+
*/
34+
tooltipText?: string
35+
}
36+
37+
const StatefulCheckbox: React.FC<CheckboxProps> = ({ label, className, checked, hideLabel, onClick, tooltipText }) => {
38+
return (
39+
<Box
40+
display="flex"
41+
flexDirection={"row"}
42+
justifyContent={"space-between"}
43+
alignItems={"center"}
44+
textAlign={"center"}
45+
>
46+
{hideLabel ? null : tooltipText ? (
47+
LabelWithTooltip(label, tooltipText)
48+
) : (
49+
<Label size={LabelSize.SMALL} className={`mr-12 ${className} whitespace-nowrap`}>
50+
{label}
51+
</Label>
52+
)}
53+
<Switch
54+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onClick && onClick(e.target.checked)}
55+
{...SoundPlayer.checkboxSoundEffects()}
56+
slotProps={{
57+
root: {
58+
className: `group relative inline-block w-[24px] h-[24px] m-2.5 cursor-pointer transform transition-transform hover:scale-[1.03] active:scale-[1.06]`,
59+
},
60+
input: {
61+
className: `cursor-inherit absolute w-full h-full top-0 left-0 opacity-0 z-10 border-none`,
62+
},
63+
track: ownerState => {
64+
return {
65+
className: `absolute block w-full h-full transition rounded-full border border-solid outline-none border-interactive-element-right dark:border-interactive-element-right group-[.base--focusVisible]:shadow-outline-switch ${ownerState.checked ? "bg-gradient-to-br from-interactive-element-left to-interactive-element-right" : "bg-background-secondary"} transform transition-transform group-hover:scale-[1.03] group-active:scale-[1.06]`,
66+
}
67+
},
68+
thumb: {
69+
className: `display-none`,
70+
},
71+
}}
72+
checked={checked}
73+
role="checkbox"
74+
/>
75+
</Box>
76+
)
77+
}
78+
79+
export default StatefulCheckbox

fission/src/ui/panels/RobotSwitchPanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import Panel, { PanelPropsImpl } from "@/components/Panel"
44
import Stack, { StackDirection } from "@/components/Stack"
55
import Button from "@/components/Button"
66
import { useModalControlContext } from "@/ui/helpers/UseModalManager"
7-
import Checkbox from "@/components/Checkbox"
87
import { SynthesisIcons } from "../components/StyledComponents"
8+
import StatefulCheckbox from "@/components/StatefulCheckbox.tsx"
99

1010
const RobotSwitchPanel: React.FC<PanelPropsImpl> = ({ panelId, openLocation, sidePadding }) => {
1111
const [robots, setRobots] = useState(["Dozer_v9_0", "Team 2471 (2018) v7_0"])
@@ -23,12 +23,12 @@ const RobotSwitchPanel: React.FC<PanelPropsImpl> = ({ panelId, openLocation, sid
2323
<form>
2424
<fieldset>
2525
{robots.map((name: string, i: number) => (
26-
<Checkbox
26+
// fixme: new checkbox
27+
<StatefulCheckbox
2728
label={name}
28-
defaultState={i == selected}
29+
checked={i == selected}
2930
className="whitespace-nowrap"
3031
onClick={() => setSelected(i)}
31-
stateOverride={i == selected}
3232
key={i}
3333
/>
3434
))}

0 commit comments

Comments
 (0)