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

Commit 70f553c

Browse files
vandyliuxnkevinnguyenandreamah
authored
micro:bit key presses functionality (accessibility) (#222)
Co-authored-by: Kevin Nguyen <xuannamkevin@gmail.com> Co-authored-by: Andrea Mah <31675041+andreamah@users.noreply.github.com>
1 parent 1e839d9 commit 70f553c

12 files changed

Lines changed: 261 additions & 59 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Device Simulator Express provides several commands in the Command Palette (F1 or
145145

146146
In Device Simulator Express, you can use keyboard to interact with the device:
147147

148-
- Push Button `A & B: A B`
148+
- Push Button `A for A, B for B, C for A & B`
149149
- Capacitive Touch Sensor `A1 – A7: SHIFT + 1~7`
150150
- Slider Switch: `SHIFT + S`
151151
- Refresh the simulator: `SHIFT + R`
@@ -175,7 +175,7 @@ Using the simulator for the micro:bit is similar to using the one for the CPX. T
175175
Please review the CPX's ["How to use" guide](#How-to-use) for more info.
176176

177177
### Keybindings
178-
- Push Button `A & B: A B`
178+
- Push Button `A for A, B for B, C for A & B`
179179
- Refresh the simulator: `SHIFT + R`
180180

181181
## Contribute

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@
171171
"type": "boolean",
172172
"default": false,
173173
"description": "%deviceSimulatorExpressExtension.configuration.properties.previewMode%",
174-
"scope": "resource"
174+
"scope": "resource"
175175
}
176176
}
177177
},
@@ -351,4 +351,4 @@
351351
"extensionDependencies": [
352352
"ms-python.python"
353353
]
354-
}
354+
}

src/adafruit_circuitplayground/constants.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,6 @@
2727

2828
VALID_PIXEL_ASSIGN_ERROR = "The pixel color value should be a tuple with three values between 0 and 255 or a hexadecimal color between 0x000000 and 0xFFFFFF."
2929

30-
TELEMETRY_EVENT_NAMES = {
31-
"TAPPED": "API.TAPPED",
32-
"PLAY_FILE": "API.PLAY.FILE",
33-
"PLAY_TONE": "API.PLAY.TONE",
34-
"START_TONE": "API.START.TONE",
35-
"STOP_TONE": "API.STOP.TONE",
36-
"DETECT_TAPS": "API.DETECT.TAPS",
37-
"ADJUST_THRESHOLD": "API.ADJUST.THRESHOLD",
38-
"RED_LED": "API.RED.LED",
39-
"PIXELS": "API.PIXELS",
40-
}
4130
ERROR_SENDING_EVENT = "Error trying to send event to the process : "
4231

4332
TIME_DELAY = 0.03

src/view/components/cpx/CpxImage.tsx

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,42 @@ export class CpxImage extends React.Component<IProps, any> {
2727
initSvgStyle(svgElement, this.props.brightness);
2828
setupButtons(this.props);
2929
setupPins(this.props);
30-
setupKeyPresses(this.props.onKeyEvent);
30+
this.setupKeyPresses(this.props.onKeyEvent);
3131
setupSwitch(this.props);
3232
this.updateImage();
3333
}
3434
}
35+
componentWillUnmount() {
36+
window.document.removeEventListener("keydown", this.handleKeyDown);
37+
window.document.removeEventListener("keyup", this.handleKeyUp);
38+
}
3539
componentDidUpdate() {
3640
this.updateImage();
3741
}
42+
setupKeyPresses = (
43+
onKeyEvent: (event: KeyboardEvent, active: boolean) => void
44+
) => {
45+
window.document.addEventListener("keydown", this.handleKeyDown);
46+
window.document.addEventListener("keyup", this.handleKeyUp);
47+
};
48+
49+
handleKeyDown = (event: KeyboardEvent) => {
50+
const keyEvents = [event.key, event.code];
51+
// Don't listen to keydown events for the switch, run button, restart button and enter key
52+
if (
53+
!(
54+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.S) ||
55+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) ||
56+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_R) ||
57+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.ENTER)
58+
)
59+
) {
60+
this.props.onKeyEvent(event, true);
61+
}
62+
};
63+
handleKeyUp = (event: KeyboardEvent) => {
64+
this.props.onKeyEvent(event, false);
65+
};
3866
render() {
3967
return CPX_SVG;
4068
}
@@ -309,32 +337,18 @@ const setupButton = (button: HTMLElement, className: string, props: IProps) => {
309337
}
310338
svgButton.onmousedown = e => props.onMouseDown(button, e);
311339
svgButton.onmouseup = e => props.onMouseUp(button, e);
312-
svgButton.onkeydown = e => props.onKeyEvent(e, true);
340+
svgButton.onkeydown = e => {
341+
// ensure that the keydown is enter.
342+
// Or else, if the key is a shortcut instead,
343+
// it may register shortcuts twice
344+
if (e.key === CONSTANTS.KEYBOARD_KEYS.ENTER) {
345+
props.onKeyEvent(e, true);
346+
}
347+
};
313348
svgButton.onkeyup = e => props.onKeyEvent(e, false);
314349
svgButton.onmouseleave = e => props.onMouseLeave(button, e);
315350
};
316351

317-
const setupKeyPresses = (
318-
onKeyEvent: (event: KeyboardEvent, active: boolean) => void
319-
) => {
320-
window.document.addEventListener("keydown", event => {
321-
const keyEvents = [event.key, event.code];
322-
// Don't listen to keydown events for the switch, run button and enter key
323-
if (
324-
!(
325-
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.S) ||
326-
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) ||
327-
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.ENTER)
328-
)
329-
) {
330-
onKeyEvent(event, true);
331-
}
332-
});
333-
window.document.addEventListener("keyup", event =>
334-
onKeyEvent(event, false)
335-
);
336-
};
337-
338352
const setupSwitch = (props: IProps): void => {
339353
const switchElement = window.document.getElementById("SWITCH");
340354
const swInnerElement = window.document.getElementById("SWITCH_INNER");

src/view/components/cpx/CpxSimulator.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class Simulator extends React.Component<{}, IState> {
132132

133133
render() {
134134
const playStopImage = this.state.play_button ? StopLogo : PlayLogo;
135+
const playStopLabel = this.state.play_button ? "stop" : "play";
135136
return (
136137
<div className="simulator">
137138
<div className="file-selector">
@@ -161,32 +162,33 @@ class Simulator extends React.Component<{}, IState> {
161162
onTogglePlay={this.togglePlayClick}
162163
onToggleRefresh={this.refreshSimulatorClick}
163164
playStopImage={playStopImage}
165+
playStopLabel={playStopLabel}
164166
/>
165167
</div>
166168
);
167169
}
168170

169171
protected togglePlayClick() {
170-
sendMessage(WEBVIEW_MESSAGES.TOGGLE_PLAY_STOP, {
171-
selected_file: this.state.selected_file,
172-
state: !this.state.play_button,
173-
});
174172
const button =
175173
window.document.getElementById(CONSTANTS.ID_NAME.PLAY_BUTTON) ||
176174
window.document.getElementById(CONSTANTS.ID_NAME.STOP_BUTTON);
177175
if (button) {
178176
button.focus();
179177
}
178+
sendMessage(WEBVIEW_MESSAGES.TOGGLE_PLAY_STOP, {
179+
selected_file: this.state.selected_file,
180+
state: !this.state.play_button,
181+
});
180182
}
181183

182184
protected refreshSimulatorClick() {
183-
sendMessage(WEBVIEW_MESSAGES.REFRESH_SIMULATOR, true);
184185
const button = window.document.getElementById(
185186
CONSTANTS.ID_NAME.REFRESH_BUTTON
186187
);
187188
if (button) {
188189
button.focus();
189190
}
191+
sendMessage(WEBVIEW_MESSAGES.REFRESH_SIMULATOR, true);
190192
}
191193

192194
protected onSelectBlur(event: React.FocusEvent<HTMLSelectElement>) {
@@ -216,6 +218,12 @@ class Simulator extends React.Component<{}, IState> {
216218
element = window.document.getElementById(
217219
CONSTANTS.ID_NAME.BUTTON_B
218220
);
221+
} else if (
222+
[event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.C)
223+
) {
224+
element = window.document.getElementById(
225+
CONSTANTS.ID_NAME.BUTTON_AB
226+
);
219227
} else if (
220228
[event.code, event.key].includes(CONSTANTS.KEYBOARD_KEYS.S)
221229
) {

src/view/components/microbit/MicrobitImage.tsx

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import * as React from "react";
55
import { VIEW_STATE } from "../../constants";
66
import { ViewStateContext } from "../../context";
7+
import CONSTANTS, { MICROBIT_BUTTON_STYLING_CLASSES } from "../../constants";
78
import "../../styles/Microbit.css";
89
import { IRefObject, MicrobitSvg } from "./Microbit_svg";
910

1011
interface EventTriggers {
1112
onMouseUp: (event: Event, buttonKey: string) => void;
1213
onMouseDown: (event: Event, buttonKey: string) => void;
1314
onMouseLeave: (event: Event, buttonKey: string) => void;
15+
onKeyEvent: (event: KeyboardEvent, active: boolean, key: string) => void;
1416
}
1517
interface IProps {
1618
eventTriggers: EventTriggers;
@@ -22,6 +24,11 @@ const BUTTON_CLASSNAME = {
2224
DEACTIVATED: "sim-button-deactivated",
2325
};
2426

27+
export enum BUTTONS_KEYS {
28+
BTN_A = "BTN_A",
29+
BTN_B = "BTN_B",
30+
BTN_AB = "BTN_AB",
31+
}
2532
// Displays the SVG and call necessary svg modification.
2633
export class MicrobitImage extends React.Component<IProps, {}> {
2734
private svgRef: React.RefObject<MicrobitSvg> = React.createRef();
@@ -33,6 +40,7 @@ export class MicrobitImage extends React.Component<IProps, {}> {
3340
if (svgElement) {
3441
updateAllLeds(this.props.leds, svgElement.getLeds());
3542
setupAllButtons(this.props.eventTriggers, svgElement.getButtons());
43+
this.setupKeyPresses(this.props.eventTriggers.onKeyEvent);
3644
}
3745
}
3846
componentDidUpdate() {
@@ -48,9 +56,56 @@ export class MicrobitImage extends React.Component<IProps, {}> {
4856
}
4957
}
5058
}
59+
componentWillUnmount() {
60+
window.document.removeEventListener("keydown", this.handleKeyDown);
61+
window.document.removeEventListener("keyup", this.handleKeyUp);
62+
}
63+
setupKeyPresses = (
64+
onKeyEvent: (event: KeyboardEvent, active: boolean, key: string) => void
65+
) => {
66+
window.document.addEventListener("keydown", this.handleKeyDown);
67+
window.document.addEventListener("keyup", this.handleKeyUp);
68+
};
69+
handleKeyDown = (event: KeyboardEvent) => {
70+
const keyEvents = [event.key, event.code];
71+
// Don't listen to keydown events for the run button, restart button and enter key
72+
if (
73+
!(
74+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_F) ||
75+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.CAPITAL_R) ||
76+
keyEvents.includes(CONSTANTS.KEYBOARD_KEYS.ENTER)
77+
)
78+
) {
79+
this.props.eventTriggers.onKeyEvent(event, true, event.key);
80+
}
81+
};
82+
handleKeyUp = (event: KeyboardEvent) => {
83+
this.props.eventTriggers.onKeyEvent(event, false, event.key);
84+
};
5185
render() {
5286
return <MicrobitSvg ref={this.svgRef} />;
5387
}
88+
public updateButtonAttributes(key: BUTTONS_KEYS, isActive: boolean) {
89+
if (this.svgRef.current) {
90+
const button = this.svgRef.current.getButtons()[key].current;
91+
if (button) {
92+
button.focus();
93+
if (isActive) {
94+
button.children[0].setAttribute(
95+
"class",
96+
MICROBIT_BUTTON_STYLING_CLASSES.KEYPRESSED
97+
);
98+
} else {
99+
button.children[0].setAttribute(
100+
"class",
101+
MICROBIT_BUTTON_STYLING_CLASSES.DEFAULT
102+
);
103+
}
104+
button.setAttribute("pressed", `${isActive}`);
105+
button.setAttribute("aria-pressed", `${isActive}`);
106+
}
107+
}
108+
}
54109
}
55110

56111
MicrobitImage.contextType = ViewStateContext;
@@ -59,8 +114,8 @@ const setupButton = (
59114
eventTriggers: EventTriggers,
60115
key: string
61116
) => {
62-
buttonElement.setAttribute("class", BUTTON_CLASSNAME.ACTIVE);
63117
buttonElement.onmousedown = e => {
118+
buttonElement.focus();
64119
eventTriggers.onMouseDown(e, key);
65120
};
66121
buttonElement.onmouseup = e => {
@@ -69,6 +124,16 @@ const setupButton = (
69124
buttonElement.onmouseleave = e => {
70125
eventTriggers.onMouseLeave(e, key);
71126
};
127+
buttonElement.onkeydown = e => {
128+
// ensure that the keydown is enter,
129+
// or else it may register shortcuts twice
130+
if (e.key === CONSTANTS.KEYBOARD_KEYS.ENTER) {
131+
eventTriggers.onKeyEvent(e, true, key);
132+
}
133+
};
134+
buttonElement.onkeyup = e => {
135+
eventTriggers.onKeyEvent(e, false, key);
136+
};
72137
};
73138
const setupAllButtons = (
74139
eventTriggers: EventTriggers,
@@ -87,6 +152,8 @@ const disableAllButtons = (buttonRefs: IRefObject) => {
87152
ref.current.onmousedown = null;
88153
ref.current.onmouseup = null;
89154
ref.current.onmouseleave = null;
155+
ref.current.onkeydown = null;
156+
ref.current.onkeyup = null;
90157
ref.current.setAttribute("class", BUTTON_CLASSNAME.DEACTIVATED);
91158
}
92159
}

0 commit comments

Comments
 (0)