Skip to content

Commit

Permalink
Add joy
Browse files Browse the repository at this point in the history
  • Loading branch information
rafal-gorecki committed Apr 11, 2024
1 parent 74c6f22 commit 17086d2
Show file tree
Hide file tree
Showing 13 changed files with 721 additions and 64 deletions.
71 changes: 42 additions & 29 deletions demo/foxglove-layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@
"foxglovePanelTitle": "E-stop",
"buttonText": "STOP",
"serviceName": "panther/hardware/e-stop",
"buttonColor": "#ff0000"
"buttonColor": "#bb0000"
},
"TriggerButton!194be9": {
"requestPayload": "{}",
"layout": "horizontal",
"advancedView": false,
"foxglovePanelTitle": "E-stop",
"buttonText": "Enable",
"buttonColor": "#00ff00",
"buttonColor": "#009900",
"serviceName": "/hardware"
},
"Tab!1l9vxsv": {
"activeTabIdx": 0,
"activeTabIdx": 1,
"tabs": [
{
"title": "Slow mode",
"layout": "Teleop!1qlq2qt"
"layout": "Joy!3fmstz6"
},
{
"title": "Fast mode",
"layout": "Teleop!1mtrw1e"
"layout": "Joy!7eprst"
}
]
},
Expand All @@ -47,57 +47,68 @@
"Indicator!2wjfnhj": {
"path": "/hardware/e_stop.data",
"style": "background",
"fallbackColor": "#ff0000",
"fallbackColor": "#bb0000",
"fallbackLabel": "Stopped",
"rules": [
{
"operator": "=",
"rawValue": "true",
"color": "#68e24a",
"color": "#00aa00",
"label": "Working"
}
],
"foxglovePanelTitle": "E-stop Status"
},
"Teleop!1qlq2qt": {
"Joy!3fmstz6": {
"topic": "/cmd_vel",
"publishRate": 1,
"publishRate": 5,
"upButton": {
"field": "linear-x",
"value": 0.5
"value": 1
},
"downButton": {
"field": "linear-x",
"value": -0.5
"value": -1
},
"leftButton": {
"field": "angular-z",
"value": 0.7
"value": 1
},
"rightButton": {
"field": "angular-z",
"value": -0.7
"value": 0
},
"foxglovePanelTitle": ""
},
"Teleop!1mtrw1e": {
"topic": "/cmd_vel",
"publishRate": 1,
"upButton": {
"xValue": {
"field": "linear-x",
"value": 1.5
"value": 1
},
"downButton": {
"yValue": {
"field": "angular-z",
"value": -1
},
"xAxis": {
"field": "linear-x",
"value": -1.5
"maxSpeed": 0.5,
"minSpeed": 0.5
},
"leftButton": {
"yAxis": {
"field": "angular-z",
"value": 3
"maxSpeed": 0.75,
"minSpeed": -0.75
}
},
"Joy!7eprst": {
"topic": "/cmd_vel",
"publishRate": 5,
"xAxis": {
"field": "linear-x",
"maxSpeed": 2,
"minSpeed": -2
},
"rightButton": {
"yAxis": {
"field": "angular-z",
"value": -3
"maxSpeed": 2.5,
"minSpeed": -2.5
}
},
"3D!2a7yeqc": {
Expand Down Expand Up @@ -272,7 +283,7 @@
},
"second": "Tab!1l9vxsv",
"direction": "column",
"splitPercentage": 21.976401179941004
"splitPercentage": 21.242937853107343
},
"second": {
"first": "Battery!wppv5y",
Expand All @@ -281,7 +292,7 @@
"splitPercentage": 21.976401179941004
},
"direction": "row",
"splitPercentage": 74.1822429906542
"splitPercentage": 76.81272643821838
}
},
{
Expand All @@ -300,7 +311,9 @@
]
}
},
"globalVariables": {},
"globalVariables": {
"": "\"\""
},
"userNodes": {},
"layout": "Tab!62jad4"
}
2 changes: 2 additions & 0 deletions packages/studio-base/src/i18n/en/panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const panels = {
imageDescription: "Display annotated images.",
indicator: "Indicator",
indicatorDescription: "Display a colored and/or textual indicator based on a threshold value.",
joy: "Joy",
joyDescription: "Teleoperate a robot over a live connection.",
log: "Log",
logDescription: "Display logs by node and severity level.",
map: "Map",
Expand Down
2 changes: 1 addition & 1 deletion packages/studio-base/src/panels/Battery/Battery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { simpleGetMessagePathDataItems } from "@foxglove/studio-base/components/
import { settingsActionReducer, useSettingsTree } from "./settings";
import type { Config } from "./types";

import "./styles.css"; // Assuming you have the CSS styles in a separate file
import "./styles.css";

type Props = {
context: PanelExtensionContext;
Expand Down
25 changes: 25 additions & 0 deletions packages/studio-base/src/panels/Joy/DirectionalPad.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import { action } from "@storybook/addon-actions";
import { StoryObj } from "@storybook/react";

import DirectionalPad from "./DirectionalPad";

export default {
title: "panels/Teleop/DirectionalPad",
component: DirectionalPad,
};

export const Basic: StoryObj = {
render: () => {
return <DirectionalPad onAction={action("click")} />;
},
};

export const Disabled: StoryObj = {
render: () => {
return <DirectionalPad disabled onAction={action("click")} />;
},
};
111 changes: 111 additions & 0 deletions packages/studio-base/src/panels/Joy/DirectionalPad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import React, { useCallback, useState, useRef } from "react";

import Stack from "@foxglove/studio-base/components/Stack";
import "./styles.css";

// Type for the Joystick Props
type DirectionalPadProps = {
disabled?: boolean;
onSpeedChange?: (pos: { x: number; y: number }) => void;
};

// Component for the DirectionalPad
function DirectionalPad(props: DirectionalPadProps): JSX.Element {
const { onSpeedChange, disabled = false } = props;
const [speed, setSpeed] = useState<{ x: number; y: number } | undefined>();
const [startPos, setStartPos] = useState<{ x: number; y: number } | undefined>();
const [isDragging, setIsDragging] = useState(false);
const joystickHeadRef = useRef<HTMLDivElement>(null); // Ref for the joystick head

// Mouse down event to initiate dragging
const handleMouseDownOnJoystick = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
setIsDragging(true);
setStartPos({ x: event.clientX, y: event.clientY });
if (joystickHeadRef.current) {
joystickHeadRef.current.style.cursor = 'grabbing';
joystickHeadRef.current.style.animation = 'none';
}
}, []);


const handleMouseMove = useCallback((event: MouseEvent) => {
if (isDragging && startPos && joystickHeadRef.current) {
let dx = event.clientX - startPos.x;
let dy = event.clientY - startPos.y;

// Calculate the distance from the center to the new position
const distance = Math.sqrt(dx * dx + dy * dy);
const maxDistance = 130; // Assuming the joystick can move 75px in any direction from the center

// If the distance is more than allowed, clamp it to the circular boundary
if (distance > maxDistance) {
dx *= maxDistance / distance;
dy *= maxDistance / distance;
}

const v_x = Math.round(-dy)/maxDistance;
const v_y = Math.round(-dx)/maxDistance;

setSpeed({x: v_x, y: v_y});
if(!disabled)
{
onSpeedChange?.({x: v_x, y: v_y});
}

joystickHeadRef.current.style.transform = `translate(${dx}px, ${dy}px)`;
}
}, [isDragging, startPos, onSpeedChange, disabled]);

// Mouse up event to end dragging
const handleMouseUp = useCallback(() => {
if (speed != undefined || isDragging) {
setIsDragging(false);
setSpeed(undefined);
props.onSpeedChange?.({x: 0, y: 0});
if (joystickHeadRef.current) {
joystickHeadRef.current.style.cursor = '';
joystickHeadRef.current.style.transform = '';
// joystickHeadRef.current.style.animation = 'glow';
}
}
}, [isDragging, speed, props]);

// UseEffect hook to add and remove the global event listeners
React.useEffect(() => {
if (isDragging) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}

return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, handleMouseMove, handleMouseUp]);

return (
<Stack justifyContent="center" alignItems="center" fullWidth fullHeight>
<div id="center">
<div id="game">
<div id="joystick">
<div className="joystick-arrow"></div>
<div className="joystick-arrow"></div>
<div className="joystick-arrow"></div>
<div className="joystick-arrow"></div>
<div id="joystick-head" ref={joystickHeadRef} onMouseDown={handleMouseDownOnJoystick}></div>
</div>
{/* Note below joystick for action feedback */}
<div id="note">
X: {speed?.x.toFixed(2) ?? '0.00'} Y: {speed?.y.toFixed(2) ?? '0.00'}
</div>
</div>
</div>
</Stack>
);
}

export default DirectionalPad;
Loading

0 comments on commit 17086d2

Please sign in to comment.