@ -1,7 +1,10 @@
|
||||
namespace pxsim.visuals {
|
||||
const MB_STYLE = `
|
||||
svg.sim {
|
||||
margin-bottom:1em;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
svg.sim.grayscale {
|
||||
-moz-filter: grayscale(1);
|
||||
@ -136,6 +139,17 @@ namespace pxsim.visuals {
|
||||
.sim-wireframe .sim-board {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
*:focus .sim-button-outer,
|
||||
.sim-pin:focus,
|
||||
.sim-thermometer:focus,
|
||||
.sim-shake:focus,
|
||||
.sim-light-level-button:focus {
|
||||
stroke: #4D90FE;
|
||||
stroke-width: 5px !important;
|
||||
}
|
||||
.no-drag {
|
||||
user-drag: none;
|
||||
user-select: none;
|
||||
@ -148,7 +162,14 @@ namespace pxsim.visuals {
|
||||
const MB_HIGHCONTRAST = `
|
||||
.sim-led {
|
||||
stroke: red;
|
||||
}
|
||||
}
|
||||
*:focus .sim-button-outer,
|
||||
.sim-pin:focus,
|
||||
.sim-thermometer:focus,
|
||||
.sim-shake:focus,
|
||||
.sim-light-level-button:focus {
|
||||
stroke: #10C8CD !important;
|
||||
}
|
||||
`
|
||||
const pins4onXs = [66.7, 79.1, 91.4, 103.7, 164.3, 176.6, 188.9, 201.3, 213.6, 275.2, 287.5, 299.8, 312.1, 324.5, 385.1, 397.4, 409.7, 422];
|
||||
const pins4onMids = pins4onXs.map(x => x + 5);
|
||||
@ -396,7 +417,8 @@ namespace pxsim.visuals {
|
||||
private updateGestures() {
|
||||
let state = this.board;
|
||||
if (state.accelerometerState.useShake && !this.shakeButton) {
|
||||
this.shakeButton = svg.child(this.g, "circle", { cx: 380, cy: 100, r: 16.5 }) as SVGCircleElement;
|
||||
this.shakeButton = svg.child(this.g, "circle", { cx: 380, cy: 100, r: 16.5, class: "sim-shake" }) as SVGCircleElement;
|
||||
accessibility.makeFocusable(this.shakeButton);
|
||||
svg.fill(this.shakeButton, this.props.theme.virtualButtonUp)
|
||||
this.shakeButton.addEventListener(pointerEvents.down, ev => {
|
||||
let state = this.board;
|
||||
@ -411,6 +433,10 @@ namespace pxsim.visuals {
|
||||
svg.fill(this.shakeButton, this.props.theme.virtualButtonUp);
|
||||
this.board.bus.queue(DAL.MICROBIT_ID_GESTURE, 11); // GESTURE_SHAKE
|
||||
})
|
||||
accessibility.enableKeyboardInteraction(this.shakeButton, undefined, () => {
|
||||
this.board.bus.queue(DAL.MICROBIT_ID_GESTURE, 11);
|
||||
});
|
||||
accessibility.setAria(this.shakeButton, "button", "Shake the board");
|
||||
this.shakeText = svg.child(this.g, "text", { x: 400, y: 110, class: "sim-text" }) as SVGTextElement;
|
||||
this.shakeText.textContent = "SHAKE"
|
||||
}
|
||||
@ -447,6 +473,16 @@ namespace pxsim.visuals {
|
||||
if (text) text.textContent = "";
|
||||
}
|
||||
if (v) svg.setGradientValue(this.pinGradients[index], v);
|
||||
|
||||
if (pin.mode !== PinFlags.Unused) {
|
||||
accessibility.makeFocusable(this.pins[index]);
|
||||
accessibility.setAria(this.pins[index], "slider", this.pins[index].firstChild.textContent);
|
||||
this.pins[index].setAttribute("aria-valuemin", "0");
|
||||
this.pins[index].setAttribute("aria-valuemax", "1023");
|
||||
this.pins[index].setAttribute("aria-orientation", "vertical");
|
||||
this.pins[index].setAttribute("aria-valuenow", text.textContent);
|
||||
accessibility.setLiveContent(text.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
private updateTemperature() {
|
||||
@ -472,18 +508,51 @@ namespace pxsim.visuals {
|
||||
|
||||
let pt = this.element.createSVGPoint();
|
||||
svg.buttonEvents(this.thermometer,
|
||||
// move
|
||||
(ev) => {
|
||||
let cur = svg.cursorPoint(pt, this.element, ev);
|
||||
let t = Math.max(0, Math.min(1, (260 - cur.y) / 140))
|
||||
state.thermometerState.temperature = Math.floor(tmin + t * (tmax - tmin));
|
||||
this.updateTemperature();
|
||||
}, ev => { }, ev => { })
|
||||
},
|
||||
// start
|
||||
ev => { },
|
||||
// stop
|
||||
ev => { },
|
||||
// keydown
|
||||
(ev) => {
|
||||
let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode
|
||||
if (charCode === 40 || charCode === 37) { // Down/Left arrow
|
||||
state.thermometerState.temperature--;
|
||||
if (state.thermometerState.temperature < -5) {
|
||||
state.thermometerState.temperature = 50;
|
||||
}
|
||||
this.updateTemperature();
|
||||
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
|
||||
state.thermometerState.temperature++;
|
||||
if (state.thermometerState.temperature > 50) {
|
||||
state.thermometerState.temperature = -5;
|
||||
}
|
||||
this.updateTemperature();
|
||||
}
|
||||
})
|
||||
|
||||
accessibility.makeFocusable(this.thermometer);
|
||||
accessibility.setAria(this.thermometer, "slider", "Thermometer");
|
||||
this.thermometer.setAttribute("aria-valuemin", "-5");
|
||||
this.thermometer.setAttribute("aria-valuemax", "50");
|
||||
this.thermometer.setAttribute("aria-orientation", "vertical");
|
||||
this.thermometer.setAttribute("aria-valuenow", "21");
|
||||
this.thermometer.setAttribute("aria-valuetext", "21°C");
|
||||
}
|
||||
|
||||
let t = Math.max(tmin, Math.min(tmax, state.thermometerState.temperature))
|
||||
let per = Math.floor((state.thermometerState.temperature - tmin) / (tmax - tmin) * 100)
|
||||
svg.setGradientValue(this.thermometerGradient, 100 - per + "%");
|
||||
this.thermometerText.textContent = t + "°C";
|
||||
this.thermometer.setAttribute("aria-valuenow", t.toString());
|
||||
this.thermometer.setAttribute("aria-valuetext", t + "°C");
|
||||
accessibility.setLiveContent(t + "°C");
|
||||
}
|
||||
|
||||
private updateHeading() {
|
||||
@ -564,6 +633,7 @@ namespace pxsim.visuals {
|
||||
}) as SVGCircleElement;
|
||||
let pt = this.element.createSVGPoint();
|
||||
svg.buttonEvents(this.lightLevelButton,
|
||||
// move
|
||||
(ev) => {
|
||||
let pos = svg.cursorPoint(pt, this.element, ev);
|
||||
let rs = r / 2;
|
||||
@ -572,10 +642,37 @@ namespace pxsim.visuals {
|
||||
this.board.lightSensorState.lightLevel = level;
|
||||
this.applyLightLevel();
|
||||
}
|
||||
}, ev => { },
|
||||
ev => { })
|
||||
},
|
||||
// start
|
||||
ev => { },
|
||||
// stop
|
||||
ev => { },
|
||||
// keydown
|
||||
(ev) => {
|
||||
let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode
|
||||
if (charCode === 40 || charCode === 37) { // Down/Left arrow
|
||||
this.board.lightSensorState.lightLevel--;
|
||||
if (this.board.lightSensorState.lightLevel < 0) {
|
||||
this.board.lightSensorState.lightLevel = 255;
|
||||
}
|
||||
this.applyLightLevel();
|
||||
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
|
||||
this.board.lightSensorState.lightLevel++;
|
||||
if (this.board.lightSensorState.lightLevel > 255) {
|
||||
this.board.lightSensorState.lightLevel = 0;
|
||||
}
|
||||
this.applyLightLevel();
|
||||
}
|
||||
});
|
||||
this.lightLevelText = svg.child(this.g, "text", { x: 85, y: cy + r - 5, text: '', class: 'sim-text' }) as SVGTextElement;
|
||||
this.updateTheme();
|
||||
|
||||
accessibility.makeFocusable(this.lightLevelButton);
|
||||
accessibility.setAria(this.lightLevelButton, "slider", "Light level");
|
||||
this.lightLevelButton.setAttribute("aria-valuemin", "0");
|
||||
this.lightLevelButton.setAttribute("aria-valuemax", "255");
|
||||
this.lightLevelButton.setAttribute("aria-orientation", "vertical");
|
||||
this.lightLevelButton.setAttribute("aria-valuenow", "128");
|
||||
}
|
||||
|
||||
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor(state.lightSensorState.lightLevel * 100 / 255))) + '%')
|
||||
@ -586,6 +683,8 @@ namespace pxsim.visuals {
|
||||
let lv = this.board.lightSensorState.lightLevel;
|
||||
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor(lv * 100 / 255))) + '%')
|
||||
this.lightLevelText.textContent = lv.toString();
|
||||
this.lightLevelButton.setAttribute("aria-valuenow", lv.toString());
|
||||
accessibility.setLiveContent(lv.toString());
|
||||
}
|
||||
|
||||
private updateTilt() {
|
||||
@ -704,12 +803,14 @@ namespace pxsim.visuals {
|
||||
|
||||
this.buttonsOuter = []; this.buttons = [];
|
||||
|
||||
const outerBtn = (left: number, top: number) => {
|
||||
const outerBtn = (left: number, top: number, label: string) => {
|
||||
const btnr = 4;
|
||||
const btnw = 56.2;
|
||||
const btnn = 6;
|
||||
const btnnm = 10
|
||||
let btng = svg.child(this.g, "g", { class: "sim-button-group" });
|
||||
accessibility.makeFocusable(btng);
|
||||
accessibility.setAria(btng, "button", label);
|
||||
this.buttonsOuter.push(btng);
|
||||
svg.child(btng, "rect", { class: "sim-button-outer", x: left, y: top, rx: btnr, ry: btnr, width: btnw, height: btnw });
|
||||
svg.child(btng, "circle", { class: "sim-button-nut", cx: left + btnnm, cy: top + btnnm, r: btnn });
|
||||
@ -718,11 +819,11 @@ namespace pxsim.visuals {
|
||||
svg.child(btng, "circle", { class: "sim-button-nut", cx: left + btnw - btnnm, cy: top + btnnm, r: btnn });
|
||||
}
|
||||
|
||||
outerBtn(25.9, 176.4);
|
||||
outerBtn(25.9, 176.4, "A");
|
||||
this.buttons.push(svg.path(this.g, "sim-button", "M69.7,203.5c0,8.7-7,15.7-15.7,15.7s-15.7-7-15.7-15.7c0-8.7,7-15.7,15.7-15.7S69.7,194.9,69.7,203.5"));
|
||||
outerBtn(418.1, 176.4);
|
||||
outerBtn(418.1, 176.4, "B");
|
||||
this.buttons.push(svg.path(this.g, "sim-button", "M461.9,203.5c0,8.7-7,15.7-15.7,15.7c-8.7,0-15.7-7-15.7-15.7c0-8.7,7-15.7,15.7-15.7C454.9,187.8,461.9,194.9,461.9,203.5"));
|
||||
outerBtn(417, 250);
|
||||
outerBtn(417, 250, "A+B");
|
||||
this.buttons.push(svg.child(this.g, "circle", { class: "sim-button", cx: 446, cy: 278, r: 16.5 }));
|
||||
(<any>this.buttonsOuter[2]).style.visibility = "hidden";
|
||||
(<any>this.buttons[2]).style.visibility = "hidden";
|
||||
@ -831,6 +932,26 @@ namespace pxsim.visuals {
|
||||
svg.removeClass(svgpin, "touched");
|
||||
this.updatePin(pin, index);
|
||||
return false;
|
||||
},
|
||||
// keydown
|
||||
(ev: KeyboardEvent) => {
|
||||
let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode
|
||||
let state = this.board;
|
||||
let pin = state.edgeConnectorState.pins[index];
|
||||
|
||||
if (charCode === 40 || charCode === 37) { // Down/Left arrow
|
||||
pin.value -= 10;
|
||||
if (pin.value < 0) {
|
||||
pin.value = 1023;
|
||||
}
|
||||
this.updatePin(pin, index);
|
||||
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
|
||||
pin.value += 10;
|
||||
if (pin.value > 1023) {
|
||||
pin.value = 0;
|
||||
}
|
||||
this.updatePin(pin, index);
|
||||
}
|
||||
});
|
||||
})
|
||||
this.pins.slice(0, 3).forEach((btn, index) => {
|
||||
@ -851,6 +972,11 @@ namespace pxsim.visuals {
|
||||
this.board.bus.queue(state.edgeConnectorState.pins[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(state.edgeConnectorState.pins[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
accessibility.enableKeyboardInteraction(btn, undefined, () => {
|
||||
let state = this.board;
|
||||
this.board.bus.queue(state.edgeConnectorState.pins[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(state.edgeConnectorState.pins[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
});
|
||||
})
|
||||
|
||||
let bpState = this.board.buttonPairState;
|
||||
@ -873,6 +999,10 @@ namespace pxsim.visuals {
|
||||
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
accessibility.enableKeyboardInteraction(btn, undefined, () => {
|
||||
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
});
|
||||
})
|
||||
this.buttonsOuter[2].addEventListener(pointerEvents.down, ev => {
|
||||
let state = this.board;
|
||||
@ -904,6 +1034,10 @@ namespace pxsim.visuals {
|
||||
this.board.bus.queue(stateButtons[2].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(stateButtons[2].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
accessibility.enableKeyboardInteraction(this.buttonsOuter[2], undefined, () => {
|
||||
this.board.bus.queue(stateButtons[2].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(stateButtons[2].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user