diff --git a/src/AnimationControl.tsx b/src/AnimationControl.tsx index 14c4d97..3695f18 100644 --- a/src/AnimationControl.tsx +++ b/src/AnimationControl.tsx @@ -1,30 +1,13 @@ -import SkipPreviousIcon from '@mui/icons-material/SkipPrevious'; -import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -import PauseTwoToneIcon from '@mui/icons-material/PauseTwoTone'; -import SkipNextIcon from '@mui/icons-material/SkipNext'; -import Button from '@mui/material/Button'; -import ButtonGroup from '@mui/material/ButtonGroup'; import Box from '@mui/material/Box'; -import RestartAltIcon from '@mui/icons-material/RestartAlt'; -import Slider from '@mui/material/Slider'; -import RepeatIcon from '@mui/icons-material/Repeat'; -import RepeatOnIcon from '@mui/icons-material/RepeatOn'; import Stack from '@mui/material/Stack'; +import IconButton from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; -import LooksOneIcon from '@mui/icons-material/LooksOne'; -import LooksOneOutlinedIcon from '@mui/icons-material/LooksOneOutlined'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import PauseTwoToneIcon from '@mui/icons-material/PauseTwoTone'; +import SkipPreviousIcon from '@mui/icons-material/SkipPrevious'; +import SkipNextIcon from '@mui/icons-material/SkipNext'; +import MenuIcon from '@mui/icons-material/Menu'; import { useEffect } from 'react'; -import DirectionsIcon from '@mui/icons-material/Directions'; -import DirectionsOutlinedIcon from '@mui/icons-material/DirectionsOutlined'; -import FilterCenterFocusOutlinedIcon from '@mui/icons-material/FilterCenterFocusOutlined'; -import ScreenshotMonitorOutlinedIcon from '@mui/icons-material/ScreenshotMonitorOutlined'; -import StartIcon from '@mui/icons-material/Start'; -import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; -import SmartToyIcon from '@mui/icons-material/SmartToy'; -import FlagIcon from '@mui/icons-material/Flag'; -import OutlinedFlagIcon from '@mui/icons-material/OutlinedFlag'; -import PolylineIcon from '@mui/icons-material/Polyline'; -import PolylineOutlinedIcon from '@mui/icons-material/PolylineOutlined'; const STEP_SIZE_INCREMENT = 0.2; const STEP_SIZE_MAX = 10; @@ -47,13 +30,14 @@ const SHOW_GOAL_VECTORS_KEY = 'v'; interface AnimationControlProps { playAnimation: boolean; - onPlayAnimationChange: (playAnimation: boolean) => void; + onPlayChange: (play: boolean) => void; onSkipBackward: () => void; onSkipForward: () => void; + onOpenDrawer: () => void; onRestart: () => void; stepSize: number; onStepSizeChange: (speed: number) => void; - loopAnimation: boolean, + loopAnimation: boolean; onLoopAnimationChange: (loopAnimation: boolean) => void; onFitView: () => void; showAgentId: boolean; @@ -72,9 +56,10 @@ interface AnimationControlProps { function AnimationControl({ playAnimation, - onPlayAnimationChange, + onPlayChange, onSkipBackward, onSkipForward, + onOpenDrawer, onRestart, stepSize, onStepSizeChange, @@ -85,7 +70,6 @@ function AnimationControl({ onShowAgentIdChange, tracePaths, onTracePathsChange, - canScreenshot, takeScreenshot, showCellId, setShowCellId, @@ -94,178 +78,170 @@ function AnimationControl({ showGoalVectors, setShowGoalVectors, }: AnimationControlProps) { - const roundAndSetStepSize = (value: number) => { - onStepSizeChange(Number(value.toFixed(1))); - } - const handleSliderChange = (event: Event, value: number | number[]) => { - event.preventDefault(); - if (typeof value === 'number') roundAndSetStepSize(value); - }; + useEffect(() => { + const roundAndSetStepSize = (value: number) => { + onStepSizeChange(Number(value.toFixed(1))); + }; - useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (!event.ctrlKey && !event.altKey && !event.metaKey) { event.preventDefault(); } - if (event.key === STEP_BACKWARD_KEY) { - onSkipBackward(); - } else if (event.key === PLAY_PAUSE_KEY) { - onPlayAnimationChange(!playAnimation); - } else if (event.key === STEP_FORWARD_KEY) { - onSkipForward(); - } else if (event.key === RESTART_KEY) { - onRestart(); - } else if (event.key === LOOP_KEY) { - onLoopAnimationChange(!loopAnimation); - } else if (event.key === FIT_VIEW_KEY) { - onFitView(); - } else if (event.key === SHOW_AGENT_ID_KEY) { - onShowAgentIdChange(!showAgentId); - } else if (event.key === STEP_SIZE_UP_KEY && stepSize + STEP_SIZE_INCREMENT <= STEP_SIZE_MAX) { - roundAndSetStepSize(stepSize + STEP_SIZE_INCREMENT); - } else if (event.key === STEP_SIZE_DOWN_KEY && stepSize - STEP_SIZE_INCREMENT >= STEP_SIZE_MIN) { - roundAndSetStepSize(stepSize - STEP_SIZE_INCREMENT); - } else if (event.key === TRACE_PATHS_KEY) { - onTracePathsChange(!tracePaths); - } else if (event.key === SCREENSHOT_KEY) { - takeScreenshot(); - } else if (event.key === SHOW_CELL_ID_KEY) { - setShowCellId(!showCellId); - } else if (event.key === SHOW_GOALS_KEY) { - setShowGoals(!showGoals); - } else if (event.key === SHOW_GOAL_VECTORS_KEY) { - setShowGoalVectors(!showGoalVectors); + switch (event.key) { + case STEP_BACKWARD_KEY: + onSkipBackward(); + break; + case PLAY_PAUSE_KEY: + onPlayChange(!playAnimation); + break; + case STEP_FORWARD_KEY: + onSkipForward(); + break; + case RESTART_KEY: + onRestart(); + break; + case LOOP_KEY: + onLoopAnimationChange(!loopAnimation); + break; + case FIT_VIEW_KEY: + onFitView(); + break; + case SHOW_AGENT_ID_KEY: + onShowAgentIdChange(!showAgentId); + break; + case STEP_SIZE_UP_KEY: + if (stepSize + STEP_SIZE_INCREMENT <= STEP_SIZE_MAX) { + roundAndSetStepSize(stepSize + STEP_SIZE_INCREMENT); + } + break; + case STEP_SIZE_DOWN_KEY: + if (stepSize - STEP_SIZE_INCREMENT >= STEP_SIZE_MIN) { + roundAndSetStepSize(stepSize - STEP_SIZE_INCREMENT); + } + break; + case TRACE_PATHS_KEY: + onTracePathsChange(!tracePaths); + break; + case SCREENSHOT_KEY: + takeScreenshot(); + break; + case SHOW_CELL_ID_KEY: + setShowCellId(!showCellId); + break; + case SHOW_GOALS_KEY: + setShowGoals(!showGoals); + break; + case SHOW_GOAL_VECTORS_KEY: + setShowGoalVectors(!showGoalVectors); + break; } }; window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); }; - }, [playAnimation, onPlayAnimationChange, loopAnimation, onFitView, - onLoopAnimationChange, onRestart, onShowAgentIdChange, onSkipBackward, - onSkipForward, onStepSizeChange, showAgentId, stepSize, onTracePathsChange, tracePaths, - takeScreenshot, showCellId, setShowCellId, showGoals, setShowGoals, showGoalVectors, - setShowGoalVectors]); + }, [ + playAnimation, + onPlayChange, + onSkipBackward, + onSkipForward, + onRestart, + stepSize, + onStepSizeChange, + loopAnimation, + onLoopAnimationChange, + onFitView, + showAgentId, + onShowAgentIdChange, + tracePaths, + onTracePathsChange, + takeScreenshot, + showCellId, + setShowCellId, + showGoals, + setShowGoals, + showGoalVectors, + setShowGoalVectors, + ]); return ( - - - - Adjust animation step size - ({STEP_SIZE_UP_KEY}/{STEP_SIZE_DOWN_KEY}) - - } - > - - - - - - - - - - + - - + + + onPlayChange(!playAnimation)} + sx={{ + width: 48, + height: 48, + backgroundColor: 'primary.main', + '&:hover': { + backgroundColor: 'primary.dark', + } + }} + > + {playAnimation ? : } + - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + + + + + + + + + ); } diff --git a/src/App.tsx b/src/App.tsx index 1b43c99..42ccca8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,7 @@ import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; import Drawer from '@mui/material/Drawer'; -import IconButton from '@mui/material/IconButton'; -import MenuIcon from '@mui/icons-material/Menu'; import ConfigBar from './ConfigBar'; +import AnimationControl from './AnimationControl'; import Visualizer from './Visualizer'; import { Graph } from './Graph'; import { Solution } from './Solution'; @@ -32,31 +30,31 @@ function App() { const [showGoalVectors, setShowGoalVectors] = React.useState(false); const [drawerOpen, setDrawerOpen] = React.useState(true); - const handleSkipBackward = () => { + const onSkipBackward = () => { if (pixiAppRef.current?.skipBackward) { pixiAppRef.current.skipBackward(); } } - const handleSkipForward = () => { + const onSkipForward = () => { if (pixiAppRef.current?.skipForward) { pixiAppRef.current.skipForward(); } } - const handleRestart = () => { + const onRestart = () => { if (pixiAppRef.current?.restart) { pixiAppRef.current.restart(); } } - const handleFitView = () => { + const onFitView = () => { if (pixiAppRef.current?.fit) { pixiAppRef.current.fit(); } } - const handleTakeScreenshot = () => { + const onTakeScreenshot = () => { if (pixiAppRef.current?.takeScreenshot) { pixiAppRef.current.takeScreenshot(); } @@ -64,23 +62,8 @@ function App() { return ( - - setDrawerOpen(true)} - sx={{ - position: 'absolute', - top: 16, - right: 16, - zIndex: 1000, - backgroundColor: 'background.paper', - '&:hover': { - backgroundColor: 'action.hover', - }, - }} - > - - - + + - + + + setDrawerOpen(true)} + onRestart={onRestart} + stepSize={stepSize} + onStepSizeChange={setStepSize} + loopAnimation={loopAnimation} + onLoopAnimationChange={setLoopAnimation} + onFitView={onFitView} + showAgentId={showAgentId} + onShowAgentIdChange={setShowAgentId} + tracePaths={tracePaths} + onTracePathsChange={setTracePaths} + canScreenshot={canScreenshot} + takeScreenshot={onTakeScreenshot} + showCellId={showCellId} + setShowCellId={setShowCellId} + showGoals={showGoals} + setShowGoals={setShowGoals} + showGoalVectors={showGoalVectors} + setShowGoalVectors={setShowGoalVectors} + /> + @@ -111,28 +122,25 @@ function App() { graph={graph} onGraphChange={useCallback((graph: Graph | null) => setGraph(graph), [])} onSolutionChange={useCallback((solution: Solution | null) => setSolution(solution), [])} - playAnimation={playAnimation} - onPlayAnimationChange={setPlayAnimation} - onSkipBackward={handleSkipBackward} - onSkipForward={handleSkipForward} - onRestart={handleRestart} + onRestart={onRestart} stepSize={stepSize} onStepSizeChange={setStepSize} loopAnimation={loopAnimation} onLoopAnimationChange={setLoopAnimation} - onFitView={handleFitView} + onFitView={onFitView} showAgentId={showAgentId} onShowAgentIdChange={setShowAgentId} tracePaths={tracePaths} onTracePathsChange={setTracePaths} canScreenshot={canScreenshot} - takeScreenshot={handleTakeScreenshot} + takeScreenshot={onTakeScreenshot} showCellId={showCellId} setShowCellId={setShowCellId} showGoals={showGoals} setShowGoals={setShowGoals} showGoalVectors={showGoalVectors} setShowGoalVectors={setShowGoalVectors} + onCloseDrawer={() => setDrawerOpen(false)} /> diff --git a/src/ConfigBar.tsx b/src/ConfigBar.tsx index e32e9e2..ca3d6e6 100644 --- a/src/ConfigBar.tsx +++ b/src/ConfigBar.tsx @@ -1,21 +1,18 @@ -import AnimationControl from './AnimationControl'; import { Graph } from './Graph'; import { parseSolution, Solution } from './Solution'; -import { Divider, Stack, Button } from '@mui/material'; -import { MuiFileInput } from "mui-file-input"; +import { Divider, Stack, Box, IconButton } from '@mui/material'; import React, { useEffect } from 'react'; -import ClearIcon from '@mui/icons-material/Clear'; -import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; -import Tooltip from '@mui/material/Tooltip'; +import CloseIcon from '@mui/icons-material/Close'; +import QuickStartSection from './QuickStartSection'; +import FilesSection from './FilesSection'; +import SpeedControlSection from './SpeedControlSection'; +import ControlsSection from './ControlsSection'; +import DisplaySection from './DisplaySection'; interface ConfigBarProps { graph: Graph | null; onGraphChange: (graph: Graph | null) => void; onSolutionChange: (solution: Solution | null) => void; - playAnimation: boolean; - onPlayAnimationChange: (playAnimation: boolean) => void; - onSkipBackward: () => void; - onSkipForward: () => void; onRestart: () => void; stepSize: number; onStepSizeChange: (speed: number) => void; @@ -34,16 +31,13 @@ interface ConfigBarProps { setShowGoals: (showGoals: boolean) => void; showGoalVectors: boolean; setShowGoalVectors: (showGoalVectors: boolean) => void; + onCloseDrawer: () => void; } function ConfigBar({ graph, onGraphChange, onSolutionChange, - playAnimation, - onPlayAnimationChange, - onSkipBackward, - onSkipForward, onRestart, stepSize, onStepSizeChange, @@ -62,6 +56,7 @@ function ConfigBar({ setShowGoals, showGoalVectors, setShowGoalVectors, + onCloseDrawer, }: ConfigBarProps) { const repoName = "JustinShetty/mapf-visualizer"; const [mapFile, setMapFile] = React.useState(null); @@ -142,100 +137,79 @@ function ConfigBar({ blurActiveElement(); }; - const downloadFile = (file: File) => { - const url = URL.createObjectURL(file); - const a = document.createElement('a'); - a.href = url; - a.download = file.name; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - return ( - - - - - - - -

Map

- - - }} - /> - {mapFile && - - - - } - - {mapError &&

{mapError}

} -
- - -

Solution

- - - }} - /> - {solutionFile && - - - - } - - {solutionError &&

{solutionError}

} -
- - + + + + + + + + + + + + + + + + + + + + + + + - - - {repoName} - + + + + + + {repoName} + +
); } diff --git a/src/ControlsSection.tsx b/src/ControlsSection.tsx new file mode 100644 index 0000000..c9d9c9f --- /dev/null +++ b/src/ControlsSection.tsx @@ -0,0 +1,71 @@ +import { Stack, Button, Box } from '@mui/material'; +import StartIcon from '@mui/icons-material/Start'; +import RepeatIcon from '@mui/icons-material/Repeat'; +import RepeatOnIcon from '@mui/icons-material/RepeatOn'; +import FilterCenterFocusOutlinedIcon from '@mui/icons-material/FilterCenterFocusOutlined'; +import ScreenshotMonitorOutlinedIcon from '@mui/icons-material/ScreenshotMonitorOutlined'; + +interface ControlsSectionProps { + onRestart: () => void; + loopAnimation: boolean; + onLoopAnimationChange: (loopAnimation: boolean) => void; + onFitView: () => void; + canScreenshot: boolean; + takeScreenshot: () => void; +} + +function ControlsSection({ + onRestart, + loopAnimation, + onLoopAnimationChange, + onFitView, + canScreenshot, + takeScreenshot, +}: ControlsSectionProps) { + return ( + + Controls + + + + + + + + ); +} + +export default ControlsSection; diff --git a/src/DisplaySection.tsx b/src/DisplaySection.tsx new file mode 100644 index 0000000..74bdc40 --- /dev/null +++ b/src/DisplaySection.tsx @@ -0,0 +1,81 @@ +import { Stack, Box, Checkbox } from '@mui/material'; +import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; +import LooksOneOutlinedIcon from '@mui/icons-material/LooksOneOutlined'; +import DirectionsOutlinedIcon from '@mui/icons-material/DirectionsOutlined'; +import OutlinedFlagIcon from '@mui/icons-material/OutlinedFlag'; +import PolylineOutlinedIcon from '@mui/icons-material/PolylineOutlined'; + +interface DisplaySectionProps { + showAgentId: boolean; + onShowAgentIdChange: (showAgentId: boolean) => void; + showCellId: boolean; + setShowCellId: (showCellId: boolean) => void; + tracePaths: boolean; + onTracePathsChange: (tracePaths: boolean) => void; + showGoals: boolean; + setShowGoals: (showGoals: boolean) => void; + showGoalVectors: boolean; + setShowGoalVectors: (showGoalVectors: boolean) => void; +} + +function DisplaySection({ + showAgentId, + onShowAgentIdChange, + showCellId, + setShowCellId, + tracePaths, + onTracePathsChange, + showGoals, + setShowGoals, + showGoalVectors, + setShowGoalVectors, +}: DisplaySectionProps) { + return ( + + Display + + onShowAgentIdChange(!showAgentId)}> + onShowAgentIdChange(e.target.checked)} sx={{ py: 0.5 }} /> + + + Agent IDs + + + + setShowCellId(!showCellId)}> + setShowCellId(e.target.checked)} sx={{ py: 0.5 }} /> + + + Cell IDs + + + + onTracePathsChange(!tracePaths)}> + onTracePathsChange(e.target.checked)} sx={{ py: 0.5 }} /> + + + Paths + + + + setShowGoals(!showGoals)}> + setShowGoals(e.target.checked)} sx={{ py: 0.5 }} /> + + + Goals + + + + setShowGoalVectors(!showGoalVectors)}> + setShowGoalVectors(e.target.checked)} sx={{ py: 0.5 }} /> + + + Vectors + + + + + ); +} + +export default DisplaySection; diff --git a/src/FilesSection.tsx b/src/FilesSection.tsx new file mode 100644 index 0000000..136de6c --- /dev/null +++ b/src/FilesSection.tsx @@ -0,0 +1,99 @@ +import { Stack, Box, Button, Tooltip } from '@mui/material'; +import { MuiFileInput } from "mui-file-input"; +import ClearIcon from '@mui/icons-material/Clear'; +import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; + +interface FilesSectionProps { + mapFile: File | null; + onMapChange: (file: File | null) => void; + mapError: string | null; + solutionFile: File | null; + onSolutionChange: (file: File | null) => void; + solutionError: string | null; +} + +function FilesSection({ + mapFile, + onMapChange, + mapError, + solutionFile, + onSolutionChange, + solutionError, +}: FilesSectionProps) { + const downloadFile = (file: File) => { + const url = URL.createObjectURL(file); + const a = document.createElement('a'); + a.href = url; + a.download = file.name; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + return ( + + + Map + + + }} + /> + {mapFile && ( + + + + )} + + {mapError && {mapError}} + + + + Solution + + + }} + /> + {solutionFile && ( + + + + )} + + {solutionError && {solutionError}} + + + ); +} + +export default FilesSection; diff --git a/src/PixiApp.tsx b/src/PixiApp.tsx index 078ee9d..6606e4c 100644 --- a/src/PixiApp.tsx +++ b/src/PixiApp.tsx @@ -385,15 +385,16 @@ const PixiApp = forwardRef(({ new PIXI.Text({ x: width / 100, y: height / 100, + anchor: new PIXI.Point(0, 0), style: textStyle, }) ); hudRef.current.addChild( new PIXI.Text({ - x: width / 100, - y: height - height / 100, - anchor: new PIXI.Point(0, 1), + x: width - width / 100, + y: height / 100, + anchor: new PIXI.Point(1, 0), text: "Click and drag to pan. Scroll to zoom.", style: textStyle, }) @@ -449,8 +450,8 @@ const PixiApp = forwardRef(({ if (hudRef.current) { hudRef.current.children[0].x = width / 100; hudRef.current.children[0].y = height / 100; - hudRef.current.children[1].x = width / 100; - hudRef.current.children[1].y = height - height / 100; + hudRef.current.children[1].x = width - width / 100; + hudRef.current.children[1].y = height / 100; } fit(); } diff --git a/src/QuickStartSection.tsx b/src/QuickStartSection.tsx new file mode 100644 index 0000000..8313572 --- /dev/null +++ b/src/QuickStartSection.tsx @@ -0,0 +1,33 @@ +import { Stack, Button, Box } from '@mui/material'; + +interface QuickStartSectionProps { + onLoadDemo: (mapName: string) => void; +} + +function QuickStartSection({ onLoadDemo }: QuickStartSectionProps) { + return ( + + Quick Start + + + + + + ); +} + +export default QuickStartSection; diff --git a/src/SpeedControlSection.tsx b/src/SpeedControlSection.tsx new file mode 100644 index 0000000..818622d --- /dev/null +++ b/src/SpeedControlSection.tsx @@ -0,0 +1,39 @@ +import { Stack, Box, Slider } from '@mui/material'; + +interface SpeedControlSectionProps { + stepSize: number; + onStepSizeChange: (stepSize: number) => void; +} + +function SpeedControlSection({ stepSize, onStepSizeChange }: SpeedControlSectionProps) { + const roundAndSetStepSize = (value: number) => { + onStepSizeChange(Number(value.toFixed(1))); + }; + + const handleSliderChange = (_event: Event, value: number | number[]) => { + if (typeof value === 'number') roundAndSetStepSize(value); + }; + + return ( + + Speed + + + + {stepSize.toFixed(1)}x + + + + ); +} + +export default SpeedControlSection;