Skip to content
This repository was archived by the owner on Dec 23, 2021. It is now read-only.

Commit 3f5c81a

Browse files
Merge pull request #271 from microsoft/users/t-xunguy/gestures-microbit
Gesture features for micro:bit
2 parents 240beef + 3d5644a commit 3f5c81a

22 files changed

+595
-107
lines changed

src/extension.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import {
1616
DialogResponses,
1717
GLOBAL_ENV_VARS,
1818
HELPER_FILES,
19+
LANGUAGE_VARS,
1920
SERVER_INFO,
2021
TelemetryEventName,
21-
LANGUAGE_VARS,
2222
} from "./constants";
2323
import { CPXWorkspace } from "./cpxWorkspace";
2424
import { DebugAdapterFactory } from "./debugger/debugAdapterFactory";
@@ -31,6 +31,7 @@ import { FileSelectionService } from "./service/fileSelectionService";
3131
import { MessagingService } from "./service/messagingService";
3232
import { PopupService } from "./service/PopupService";
3333
import { SetupService } from "./service/SetupService";
34+
import { WebviewService } from "./service/webviewService";
3435
import { SimulatorDebugConfigurationProvider } from "./simulatorDebugConfigurationProvider";
3536
import getPackageInfo from "./telemetry/getPackageInfo";
3637
import TelemetryAI from "./telemetry/telemetryAI";
@@ -40,7 +41,6 @@ import {
4041
WEBVIEW_MESSAGES,
4142
WEBVIEW_TYPES,
4243
} from "./view/constants";
43-
import { WebviewService } from "./service/webviewService";
4444

4545
let telemetryAI: TelemetryAI;
4646
let pythonExecutablePath: string = GLOBAL_ENV_VARS.PYTHON;
@@ -215,7 +215,7 @@ export async function activate(context: vscode.ExtensionContext) {
215215
}
216216

217217
break;
218-
218+
case WEBVIEW_MESSAGES.GESTURE:
219219
case WEBVIEW_MESSAGES.SENSOR_CHANGED:
220220
handleGestureTelemetry(message.text);
221221
console.log(`Sensor changed ${messageJson} \n`);

src/micropython/microbit/__model/accelerometer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,14 @@ def __add_current_gesture_to_gesture_lists(self):
123123
self.__gestures.append(self.__current_gesture)
124124
self.__prev_gestures.add(self.__current_gesture)
125125

126-
def __update(self, axis, accel):
126+
def __update_motion(self, axis, accel):
127127
if accel is not None:
128128
previous_accel = self.__get_accel(axis)
129129
if accel != previous_accel:
130130
self.__set_accel(axis, accel)
131+
132+
def __update_gesture(self, gesture):
133+
if gesture is not None:
134+
previous_gesture = self.__current_gesture
135+
if previous_gesture != gesture:
136+
self.__set_gesture(gesture)

src/micropython/microbit/__model/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,5 @@
165165
EXPECTED_INPUT_LIGHT = "light"
166166

167167
EXPECTED_INPUT_TEMP = "temperature"
168+
169+
EXPECTED_INPUT_GESTURE = "gesture"

src/micropython/microbit/__model/microbit_model.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def update_state(self, new_state):
6464
self.__update_motion(new_state)
6565
self.__update_light(new_state)
6666
self.__update_temp(new_state)
67+
self.__update_gesture(new_state)
6768

6869
# helpers
6970
def __update_buttons(self, new_state):
@@ -75,7 +76,9 @@ def __update_buttons(self, new_state):
7576
def __update_motion(self, new_state):
7677
# set motion_x, motion_y, motion_z
7778
for name, direction in CONSTANTS.EXPECTED_INPUT_ACCEL.items():
78-
self.accelerometer._Accelerometer__update(direction, new_state.get(name))
79+
self.accelerometer._Accelerometer__update_motion(
80+
direction, new_state.get(name)
81+
)
7982

8083
def __update_light(self, new_state):
8184
# set light level
@@ -90,6 +93,11 @@ def __update_temp(self, new_state):
9093
if new_temp != previous_temp:
9194
self._MicrobitModel__set_temperature(new_temp)
9295

96+
def __update_gesture(self, new_state):
97+
# set gesture
98+
new_gesture = new_state.get(CONSTANTS.EXPECTED_INPUT_GESTURE)
99+
self.accelerometer._Accelerometer__update_gesture(new_gesture)
100+
93101
def __set_debug_mode(self, mode):
94102
self.display._Display__debug_mode = mode
95103

src/view/components/Dropdown.tsx

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,28 @@
22
// Licensed under the MIT license.
33

44
import * as React from "react";
5-
6-
import { CONSTANTS } from "../constants";
75
import "../styles/Dropdown.css";
86

97
export interface IDropdownProps {
10-
label: string;
11-
textOptions: string[];
12-
lastChosen: string;
13-
styleLabel: string;
14-
width: number;
15-
onBlur: (event: React.FocusEvent<HTMLSelectElement>) => void;
8+
options: string[];
9+
// styleLabel: string;
10+
onSelect?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
1611
}
1712

18-
const Dropdown: React.FC<IDropdownProps> = props => {
19-
const parsedPath = parsePath(props.lastChosen);
20-
const defaultText =
21-
props.lastChosen !== ""
22-
? CONSTANTS.CURRENTLY_RUNNING(parsedPath[1])
23-
: CONSTANTS.FILES_PLACEHOLDER;
13+
export const Dropdown: React.FC<IDropdownProps> = props => {
2414
return (
25-
<div>
26-
<select
27-
id={props.label}
28-
className={props.styleLabel}
29-
onBlur={props.onBlur}
30-
>
31-
<option value="" disabled selected>
32-
{defaultText}
33-
</option>
34-
{renderOptions(props.textOptions)}
35-
</select>
36-
</div>
15+
<select className="dropdown" onChange={props.onSelect}>
16+
{renderOptions(props.options)}
17+
</select>
3718
);
3819
};
3920

4021
const renderOptions = (options: string[]) => {
4122
return options.map((name, index) => {
42-
const key = `option-${index}`;
43-
const parsedPath = parsePath(name);
4423
return (
45-
<option key={key} value={name}>
46-
{`${parsedPath[1]} : ${parsedPath[0]}`}
24+
<option key={name} value={name}>
25+
{name}
4726
</option>
4827
);
4928
});
5029
};
51-
52-
const parsePath = (filePath: string) => {
53-
const lastSlash =
54-
filePath.lastIndexOf("/") !== -1
55-
? filePath.lastIndexOf("/")
56-
: filePath.lastIndexOf("\\");
57-
return [filePath.slice(0, lastSlash), filePath.substr(lastSlash + 1)];
58-
};
59-
60-
export default Dropdown;

src/view/components/microbit/Microbit.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@
33

44
import * as React from "react";
55
import { MICROBIT_TOOLBAR_ID } from "../../components/toolbar/SensorModalUtils";
6-
import { SENSOR_LIST, VSCODE_MESSAGES_TO_WEBVIEW } from "../../constants";
6+
import {
7+
GESTURES,
8+
SENSOR_LIST,
9+
VSCODE_MESSAGES_TO_WEBVIEW,
10+
WEBVIEW_MESSAGES,
11+
} from "../../constants";
712
import "../../styles/Simulator.css";
813
import * as TOOLBAR_SVG from "../../svgs/toolbar_svg";
14+
import { sendMessage } from "../../utils/MessageUtils";
915
import ToolBar from "../toolbar/ToolBar";
1016
import { MicrobitSimulator } from "./MicrobitSimulator";
1117

1218
// Component grouping the functionality for micro:bit functionalities
1319
interface IState {
1420
sensors: { [key: string]: number };
21+
currentSelectedGesture?: string;
1522
}
1623
const DEFAULT_STATE = {
1724
sensors: {
@@ -21,6 +28,7 @@ const DEFAULT_STATE = {
2128
[SENSOR_LIST.MOTION_Y]: 0,
2229
[SENSOR_LIST.MOTION_Z]: 0,
2330
},
31+
currentSelectedGesture: GESTURES[0],
2432
};
2533

2634
export class Microbit extends React.Component<{}, IState> {
@@ -51,13 +59,31 @@ export class Microbit extends React.Component<{}, IState> {
5159
buttonList={MICROBIT_TOOLBAR_BUTTONS}
5260
onUpdateSensor={this.updateSensor}
5361
sensorValues={this.state.sensors}
62+
onSelectGesture={this.updateGesture}
63+
sendGesture={this.sendGesture}
5464
/>
5565
</React.Fragment>
5666
);
5767
}
5868
updateSensor = (sensor: SENSOR_LIST, value: number) => {
5969
this.setState({ sensors: { ...this.state.sensors, [sensor]: value } });
6070
};
71+
updateGesture = (event: React.ChangeEvent<HTMLSelectElement>) => {
72+
this.setState({ currentSelectedGesture: event.target.value });
73+
};
74+
sendGesture = (isActive: boolean) => {
75+
if (this.state.currentSelectedGesture) {
76+
if (isActive) {
77+
sendMessage(WEBVIEW_MESSAGES.GESTURE, {
78+
gesture: this.state.currentSelectedGesture,
79+
});
80+
} else {
81+
sendMessage(WEBVIEW_MESSAGES.GESTURE, {
82+
gesture: "",
83+
});
84+
}
85+
}
86+
};
6187
}
6288

6389
const MICROBIT_TOOLBAR_BUTTONS: Array<{ label: string; image: JSX.Element }> = [

src/view/components/microbit/MicrobitSimulator.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface IState {
2929
play_button: boolean;
3030
selected_file: string;
3131
microbit: IMicrobitState;
32+
sendGesture?: (isActive: boolean) => void;
3233
}
3334

3435
interface IMicrobitState {

src/view/components/toolbar/SensorButton.tsx

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,35 @@ import * as React from "react";
55
import "../../styles/SensorButton.css";
66
import { ISensorButtonProps } from "../../viewUtils";
77

8-
const SensorButton: React.FC<ISensorButtonProps> = props => {
9-
return (
10-
<button
11-
id={`${props.label}-button`}
12-
onMouseUp={props.onMouseUp}
13-
onMouseDown={props.onMouseDown}
14-
onKeyUp={props.onKeyUp}
15-
onKeyDown={props.onKeyDown}
16-
aria-label={`${props.type} sensor button`}
17-
className="sensor-button"
18-
>
19-
{props.label}
20-
</button>
21-
);
22-
};
8+
class SensorButton extends React.Component<ISensorButtonProps> {
9+
private buttonRef: React.RefObject<HTMLButtonElement> = React.createRef();
10+
11+
public setButtonClass = (isActive: boolean) => {
12+
if (isActive) {
13+
this.buttonRef!.current!.setAttribute(
14+
"class",
15+
"sensor-button active-button"
16+
);
17+
} else {
18+
this.buttonRef!.current!.setAttribute("class", "sensor-button");
19+
}
20+
};
21+
render() {
22+
return (
23+
<button
24+
id={`${this.props.label}-button`}
25+
ref={this.buttonRef}
26+
onMouseUp={this.props.onMouseUp}
27+
onMouseDown={this.props.onMouseDown}
28+
onKeyUp={this.props.onKeyUp}
29+
onKeyDown={this.props.onKeyDown}
30+
aria-label={`${this.props.type} sensor button`}
31+
className="sensor-button"
32+
>
33+
{this.props.label}
34+
</button>
35+
);
36+
}
37+
}
2338

2439
export default SensorButton;

src/view/components/toolbar/SensorModalUtils.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,9 @@ export const TEMPERATURE_MODAL_CONTENT = (
274274

275275
export const ACCELEROMETER_MODAL_CONTENT = (
276276
onUpdateValue: (sensor: SENSOR_LIST, value: number) => void,
277-
sensorValues: { [key: string]: number }
277+
sensorValues: { [key: string]: number },
278+
onSelectGestures?: (event: React.ChangeEvent<HTMLSelectElement>) => void,
279+
sendGesture?: (isActive: boolean) => void
278280
): IModalContent => {
279281
const accelerometerSensorValues = {
280282
X_AXIS: sensorValues[SENSOR_LIST.MOTION_X],
@@ -286,6 +288,8 @@ export const ACCELEROMETER_MODAL_CONTENT = (
286288
<Accelerometer
287289
onUpdateValue={onUpdateValue}
288290
axisValues={accelerometerSensorValues}
291+
onSelectGestures={onSelectGestures}
292+
onSendGesture={sendGesture}
289293
/>
290294
),
291295
descriptionText: "toolbar-accelerometer-sensor.description",
@@ -391,12 +395,22 @@ export const LABEL_TO_MODAL_CONTENT_CONSTRUCTOR = new Map([
391395
export const getModalContent = (
392396
label: string,
393397
onUpdateValue: (onUpdateValue: SENSOR_LIST, value: number) => void,
394-
sensorValues: { [key: string]: number }
398+
sensorValues: { [key: string]: number },
399+
onSelectGestures?: (event: React.ChangeEvent<HTMLSelectElement>) => void,
400+
sendGesture?: (isActive: boolean) => void
395401
) => {
396402
const modalContentConstructor = LABEL_TO_MODAL_CONTENT_CONSTRUCTOR.get(
397403
label
398404
);
399405
if (modalContentConstructor) {
406+
if (label === MICROBIT_TOOLBAR_ID.ACCELEROMETER) {
407+
return ACCELEROMETER_MODAL_CONTENT(
408+
onUpdateValue,
409+
sensorValues,
410+
onSelectGestures,
411+
sendGesture
412+
);
413+
}
400414
return modalContentConstructor(onUpdateValue, sensorValues);
401415
} else {
402416
return;

src/view/components/toolbar/ToolBar.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
3+
import { initializeIcons } from "@uifabric/icons";
34
import { Callout, TooltipHost } from "office-ui-fabric-react";
45
import { IconButton } from "office-ui-fabric-react";
5-
import { initializeIcons } from "@uifabric/icons";
66
import * as React from "react";
77
import {
88
FormattedMessage,
@@ -31,6 +31,8 @@ interface IProps extends WrappedComponentProps {
3131
}>;
3232
onUpdateSensor: (sensor: SENSOR_LIST, value: number) => void;
3333
sensorValues: { [key: string]: number };
34+
onSelectGesture?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
35+
sendGesture?: (isActive: boolean) => void;
3436
}
3537

3638
class ToolBar extends React.Component<IProps, IToolbarState, any> {
@@ -152,7 +154,9 @@ class ToolBar extends React.Component<IProps, IToolbarState, any> {
152154
!getModalContent(
153155
this.state.currentOpenedId,
154156
this.props.onUpdateSensor,
155-
this.props.sensorValues
157+
this.props.sensorValues,
158+
this.props.onSelectGesture,
159+
this.props.sendGesture
156160
)
157161
) {
158162
return null;
@@ -161,7 +165,9 @@ class ToolBar extends React.Component<IProps, IToolbarState, any> {
161165
const content = getModalContent(
162166
this.state.currentOpenedId,
163167
this.props.onUpdateSensor,
164-
this.props.sensorValues
168+
this.props.sensorValues,
169+
this.props.onSelectGesture,
170+
this.props.sendGesture
165171
) as IModalContent;
166172

167173
const components = content

src/view/components/toolbar/Toolbar.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import * as React from "react";
22
import * as ReactDOM from "react-dom";
33
import { IntlProvider } from "react-intl";
44
import * as testRenderer from "react-test-renderer";
5-
import Toolbar from "./ToolBar";
5+
import { SENSOR_LIST } from "../../constants";
66
import * as TOOLBAR_SVG from "../../svgs/toolbar_svg";
77
import { MICROBIT_TOOLBAR_ID } from "./SensorModalUtils";
8-
import { SENSOR_LIST } from "../../constants";
8+
import Toolbar from "./ToolBar";
99

1010
const MOCK_TOOLBAR_BUTTONS: Array<{ label: string; image: JSX.Element }> = [
1111
{

0 commit comments

Comments
 (0)