namespace pxsim.visuals {
const MB_STYLE = `
.simEventBtn {
font-size: 1.4rem;
font-weight: 900;
padding: 1.25rem 1.75rem;
border-radius: 3.5rem / 100%;
border: 0;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 0.07em;
color: white;
background: #42c9c9;
font-family: 'Roboto Mono', monospace;
}
button:hover {
opacity: .7;
}
button:active {
background: #e6007d;
}
svg.sim {
margin-bottom:1em;
}
svg.sim.grayscale {
-moz-filter: grayscale(1);
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
.sim-button-group {
cursor: pointer;
}
.sim-button {
pointer-events: none;
}
.sim-board, .sim-display, sim-button {
fill: #111;
}
.sim-button-outer:hover {
stroke:grey;
stroke-width: 3px;
}
.sim-button-nut {
fill:#704A4A;
pointer-events:none;
}
.sim-button-nut:hover {
stroke:1px solid #704A4A;
}
.sim-pin {
cursor: pointer;
}
.sim-pin:hover {
stroke:#D4AF37;
stroke-width:2px;
}
.sim-pin-touch.touched:hover {
stroke:darkorange;
}
.sim-led-back:hover {
stroke:#fff;
stroke-width:3px;
}
.sim-led:hover {
stroke:#ff7f7f;
stroke-width:3px;
}
.sim-systemled {
fill:#333;
stroke:#555;
stroke-width: 1px;
}
.sim-light-level-button {
stroke:#ccc;
stroke-width: 2px;
}
.sim-antenna {
fill-opacity:0.0;
stroke:#555;
stroke-width: 4px;
}
.sim-text {
font-family: 'Roboto Mono', monospace;
font-size:14px;
fill:#fff;
pointer-events: none; user-select: none;
}
.sim-text-pin {
font-family: 'Roboto Mono', monospace;
pointer-events: none; user-select: none;
fill:#000;
font-size:24px;
stroke:#fff;
stroke-alignment: outside;
paint-order: stroke;
stroke-width: 3px;
}
.sim-thermometer {
stroke:#aaa;
stroke-width: 2px;
}
#rgbledcircle:hover {
r:8px;
}
.inverted {
fill:#000;
stroke:#fff;
stroke-alignment: outside;
paint-order: stroke;
stroke-width: 3px;
}
.big {
font-size:24px;
font-weight: bold;
}
.centered {
transform: translateX(-1.5ch);
text-align: center;
}
/* animations */
.sim-theme-glow {
animation-name: sim-theme-glow-animation;
animation-timing-function: ease-in-out;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-duration: 1.25s;
}
@keyframes sim-theme-glow-animation {
from { opacity: 1; }
to { opacity: 0.75; }
}
.sim-flash {
animation-name: sim-flash-animation;
animation-duration: 0.1s;
}
@keyframes sim-flash-animation {
from { fill: yellow; }
to { fill: default; }
}
.sim-flash-stroke {
animation-name: sim-flash-stroke-animation;
animation-duration: 0.4s;
animation-timing-function: ease-in;
}
@keyframes sim-flash-stroke-animation {
from { stroke: yellow; }
to { stroke: default; }
}
/* wireframe */
.sim-wireframe * {
fill: none;
stroke: black;
}
.sim-wireframe .sim-display,
.sim-wireframe .sim-led,
.sim-wireframe .sim-led-back,
.sim-wireframe .sim-head,
.sim-wireframe .sim-theme,
.sim-wireframe .sim-button-group,
.sim-wireframe .sim-button-label,
.sim-wireframe .sim-button,
.sim-wireframe .sim-text-pin
{
visibility: hidden;
}
.sim-wireframe .sim-label
{
stroke: none;
fill: #777;
}
.sim-label, .sim-button-label {
fill: #000;
}
.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, .sim-text, .sim-text-pin {
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.shake_animation {
animation: shake 0.42s cubic-bezier(.36,.07,.19,.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
}
@keyframes shake {
10%, 90% {
transform: translate3d(-1px, 0, 0);
}
20%, 80% {
transform: translate3d(2px, 0, 0);
}
30%, 50%, 70% {
transform: translate3d(-4px, 0, 0);
}
40%, 60% {
transform: translate3d(4px, 0, 0);
}
}
`;
const BOARD_SVG = `
`;
const pinNames = [
"EDGE_P0", "EDGE_P1", "EDGE_P2", "EDGE_P3", "EDGE_GND", "EDGE_VCC",
"BTN_A", "BTN_B",
"C_GND1", "C_GND2", "C_GND3", "C_GND4", "C_VCC1", "C_VCC2",
"C_P0", "C_P2", "C_P4", "C_P6", "C_P8", "C_P10", "C_P12", "C_P14", "C_P16", "C_P18",
"C_P1", "C_P3", "C_P5", "C_P7", "C_P9", "C_P11", "C_P13", "C_P15", "C_P17", "C_P19",
"M_GND1", "M_GND2", "M_GND3", "M_OUT1", "M_OUT2", "M_VM",
"G_A0_GND", "G_A0_VCC", "G_A0_SDA", "G_A0_SCL",
"G_A1_RX", "G_A1_TX", "G_A1_VCC", "G_A1_GND"
];
const pinTitles = [
"P0", "P1, ANALOG IN", "P2, ANALOG IN", "P3", "GND", "+3v3",
"Button A", "Button B",
"GND", "GND", "GND", "GND", "+3v3", "+3v3",
"C0", "C2", "C4", "C6", "C8", "C10", "C12", "C14", "C16", "C18",
"C1", "C3", "C5", "C7", "C9", "C11", "C13", "C15", "C17", "C19",
"GND", "GND", "GND", "MOTOR B", "MOTOR A", "MOTOR VM",
"GND", "+3v3", "C18, I2C - SDA", "C19, I2C - SCL",
"C16, Serial - RX", "C17, Serial - TX", "+3v3", "GND"
];
const MB_WIDTH = 530;
const MB_HEIGHT = 630;
export interface IBoardTheme {
accent?: string;
display?: string;
pin?: string;
pinTouched?: string;
pinActive?: string;
ledOn?: string;
ledOff?: string;
buttonOuter?: string;
buttonUps: string[];
buttonDown?: string;
virtualButtonOuter?: string;
virtualButtonUp?: string;
virtualButtonDown?: string;
lightLevelOn?: string;
lightLevelOff?: string;
soundLevelOn?: string;
soundLevelOff?: string;
}
export var themes: IBoardTheme[] = ["#3ADCFE"].map(accent => {
return {
accent: accent,
pin: "#F6C426",
pinTouched: "#FFA500",
pinActive: "#E6007D",
ledOn: "#ff5555",
ledOff: "#e0e1e2",
buttonOuter: "#979797",
buttonUps: ["#186A8C", "#D82E50"],
buttonDown: "#FFA500",
virtualButtonDown: "#FFA500",
virtualButtonOuter: "#333",
virtualButtonUp: "#fff",
lightLevelOn: "#555",
lightLevelOff: "yellow",
soundLevelOn: "#3ADCFE",
soundLevelOff: "#555"
}
});
export function randomTheme(): IBoardTheme {
return themes[Math.floor(Math.random() * themes.length)];
}
export interface IBoardProps {
runtime?: pxsim.Runtime;
theme?: IBoardTheme;
wireframe?: boolean;
disableTilt?: boolean;
}
export class MicrobitBoardSvg implements BoardView {
public element: SVGSVGElement;
private style: SVGStyleElement;
private defs: SVGDefsElement;
private g: SVGGElement;
private pkg: SVGPathElement;
private logos: SVGElement[];
private headg: SVGGElement;
private head: SVGGElement;
private headParts: SVGElement;
private headInitialized = false;
private heads: SVGElement[];
private headText: SVGTextElement;
private display: SVGElement;
private buttons: SVGElement[];
private buttonsOuter: SVGElement[];
// private buttonABText: SVGTextElement;
private pins: SVGElement[];
private pinGradients: SVGLinearGradientElement[];
private pinTexts:{ [key: number]: SVGTextElement };
private ledsOuter: SVGElement[];
private leds: SVGElement[];
private microphoneLed: SVGElement;
private systemLed: SVGCircleElement;
private antenna: SVGPolylineElement;
private rssi: SVGTextElement;
private lightLevelButton: SVGCircleElement;
private lightLevelGradient: SVGLinearGradientElement;
private lightLevelText: SVGTextElement;
private thermometerGradient: SVGLinearGradientElement;
private thermometer: SVGRectElement;
private thermometerText: SVGTextElement;
private soundLevelGradient: SVGLinearGradientElement;
private soundLevel: SVGRectElement;
private soundLevelText: SVGTextElement;
private soundLevelIcon: SVGTextElement;
private shakeButton: SVGElement;
// private shakeText: SVGTextElement;
public board: pxsim.DalBoard;
private domHardwareVersion = 1;
private rgbLed: SVGElement;
private pinNmToCoord: Map = {
"EXT_PWR": [
92.30997467041016,
-42.92474937438965
],
"SPKR": [
106.44635391235352,
-16.370698928833008
],
"BTN_A": [
93.8138427734375,
56.631452560424805
],
"BTN_B": [
204.92835235595703,
56.631452560424805
],
// rings
"EDGE_P0": [
56.002254486083984,
95.43130111694336
],
"EDGE_P1": [
103.00893783569336,
175.82388305664062
],
"EDGE_P2": [
195.90512084960938,
175.3082733154297
],
"EDGE_P3": [
241.79466247558594,
95.3883285522461
],
"EDGE_GND": [
103.00893783569336,
14.86682915687561
],
"EDGE_VCC": [
195.64733123779297,
14.86682915687561
],
"C_GND1": [
113.1493148803711,
159.83989715576172
],
"C_GND2": [
150.27342987060547,
159.83989715576172
],
"C_GND3": [
150.27342987060547,
153.5666275024414
],
"C_GND4": [
187.39752960205078,
153.5666275024414
],
"C_VCC1": [
187.39752960205078,
159.83989715576172
],
"C_VCC2": [
113.1922836303711,
153.5666275024414
],
"C_P0": [
119.33667373657227,
159.83989715576172
],
"C_P2": [
125.52401733398438,
159.83989715576172
],
"C_P4": [
131.71136474609375,
159.83989715576172
],
"C_P6": [
137.89871978759766,
159.83989715576172
],
"C_P8": [
144.08607482910156,
159.83989715576172
],
"C_P10": [
156.46077728271484,
159.83989715576172
],
"C_P12": [
162.64812469482422,
159.83989715576172
],
"C_P14": [
168.83545684814453,
159.83989715576172
],
"C_P16": [
175.02281951904297,
159.83989715576172
],
"C_P20": [
181.2101821899414,
159.83989715576172
],
"C_P1": [
119.379638671875,
153.5666275024414
],
"C_P3": [
125.56698226928711,
153.5666275024414
],
"C_P5": [
131.71136474609375,
153.5666275024414
],
"C_P7": [
137.89871978759766,
153.5666275024414
],
"C_P9": [
144.08607482910156,
153.5666275024414
],
"C_P11": [
156.46077728271484,
153.5666275024414
],
"C_P13": [
162.64812469482422,
153.5666275024414
],
"C_P15": [
168.83545684814453,
153.5666275024414
],
"C_P21": [
175.02281951904297,
153.5666275024414
],
"C_P19": [
181.2101821899414,
153.5666275024414
],
"M_GND1": [
137.89871978759766,
141.70752716064453
],
"M_GND2": [
156.46077728271484,
141.70752716064453
],
"M_GND3": [
168.83547210693360,
141.70752716064453
],
"M_OUT1": [
144.08607482910156,
141.70752716064453
],
"M_OUT2": [
150.27342987060547,
141.70752716064453
],
"M_VM": [
162.64812469482422,
141.70752716064453
],
"G_A0_GND": [
82.47036743164062,
72.35763549804688
],
"G_A0_VCC": [
78.34546279907227,
76.3106689453125
],
"G_A0_SDA": [
74.65023803710938,
80.00588989257812
],
"G_A0_SCL": [
70.43940734863281,
84.21672821044922
],
"G_A1_RX": [
216.52963256835938,
71.4982795715332
],
"G_A1_TX": [
220.65453338623047,
75.53724670410156
],
"G_A1_VCC": [
224.34976959228516,
79.23247528076172
],
"G_A1_GND": [
228.56060028076172,
83.44330978393555
]
};
constructor(public props: IBoardProps) {
this.buildDom();
if (props && props.wireframe)
U.addClass(this.element, "sim-wireframe");
if (props && props.theme)
this.updateTheme();
if (props && props.runtime) {
this.board = this.props.runtime.board as pxsim.DalBoard;
this.board.updateSubscribers.push(() => this.updateState());
this.updateState();
this.attachEvents();
}
}
public getView(): SVGAndSize {
return {
el: this.element,
y: 0,
x: 0,
w: MB_WIDTH,
h: MB_HEIGHT
};
}
public getCoord(pinNm: string): Coord {
return this.pinNmToCoord[pinNm];
}
public highlightPin(pinNm: string): void {
//TODO: for instructions
}
public getPinDist(): number {
return 10;
}
private recordPinCoords() {
pinNames.forEach((nm, i) => {
const p = this.pins[i];
const r = p.getBoundingClientRect();
this.pinNmToCoord[nm] = [r.left + r.width / 2, r.top + r.height / 2];
});
}
private updateTheme() {
let theme = this.props.theme;
svg.fills(this.leds, theme.ledOn);
svg.fills(this.ledsOuter, theme.ledOff);
svg.fills(this.buttonsOuter.slice(6, 8), theme.buttonOuter);
svg.fill(this.buttons[0], theme.buttonUps[0]);
svg.fill(this.buttons[1], theme.buttonUps[1]);
svg.fill(this.buttonsOuter[2], theme.virtualButtonOuter);
svg.fill(this.buttons[2], theme.virtualButtonUp);
if (this.shakeButton) svg.fill(this.shakeButton, theme.virtualButtonUp);
this.pinGradients.forEach(lg => svg.setGradientColors(lg, theme.pin, theme.pinActive));
svg.setGradientColors(this.lightLevelGradient, theme.lightLevelOn, theme.lightLevelOff);
svg.setGradientColors(this.soundLevelGradient, theme.soundLevelOff, theme.soundLevelOn);
svg.setGradientColors(this.thermometerGradient, theme.ledOff, theme.ledOn);
}
public updateState() {
let state = this.board;
if (!state) return;
let theme = this.props.theme;
this.updateMicrophone();
this.updatePins();
this.updateTilt();
this.updateHeading();
this.updateLightLevel();
this.updateTemperature();
this.updateButtonAB();
this.updateGestures();
this.updateRgbLed();
this.updateSpeaker();
this.updateRSSI();
let bpState = state.buttonPairState;
let buttons = [bpState.aBtn, bpState.bBtn, bpState.abBtn];
buttons.forEach((btn, index) => {
svg.fill(this.buttons[index], btn.pressed ? (btn.virtual ? theme.virtualButtonDown : theme.buttonDown) : (btn.virtual ? theme.virtualButtonUp : theme.buttonUps[index]));
});
if (state.ledMatrixState.disabled) {
this.leds.forEach((led, i) => {
const sel = (led)
sel.style.opacity = "0";
})
} else {
const bw = state.ledMatrixState.displayMode == pxsim.DisplayMode.bw
const img = state.ledMatrixState.image;
const br = state.ledMatrixState.brigthness != undefined ? state.ledMatrixState.brigthness : 255;
this.leds.forEach((led, i) => {
const sel = (led)
let imgbr = bw ? (img.data[i] > 0 ? br : 0) : img.data[i];
// correct brightness
const opacity = imgbr > 0 ? imgbr / 255 * 155 + 100 : 0;
const transfrom = imgbr > 0 ? imgbr / 255 * 0.4 + 0.6 : 0;
sel.style.opacity = (opacity / 255) + "";
if (transfrom > 0) {
(sel.style as any).transformBox = 'fill-box';
sel.style.transformOrigin = '50% 50%';
sel.style.transform = `scale(${transfrom})`;
}
})
}
if (!runtime || runtime.dead) U.addClass(this.element, "grayscale");
else U.removeClass(this.element, "grayscale");
}
private updateRgbLed() {
let state = this.board;
if (state.rgbLedState) {
if (!this.rgbLed)
this.rgbLed = this.element.getElementById("rgbledcircle") as SVGCircleElement;
const c = state.rgbLedState;
const b = c & 0xFF;
const g = (c >> 8) & 0xFF;
const r = (c >> 16) & 0xFF;
const w = (c >> 24) & 0xFF;
const ch = `rgba(${r}, ${g}, ${b}, 1)`;
svg.fill(this.rgbLed, ch);
} else if (this.rgbLed) {
svg.fill(this.rgbLed, 'white');
}
}
private updateSpeaker() {
let state = this.board;
if (state.speakerState.frequency) {
} else {
}
}
private updateGestures() {
let state = this.board;
if (state.accelerometerState.useShake && !this.shakeButton) {
let shake = this.mkBtn(240, MB_HEIGHT - 75, 'Schütteln');
this.shakeButton = shake.inner;
let board = this.element.getElementById("calliope_mini")
console.log(board)
// svg.fill(this.shakeButton, this.props.theme.virtualButtonUp)
svg.buttonEvents(shake.outer,
ev => { },
(ev) => {
svg.fill(this.shakeButton, this.props.theme.virtualButtonDown);
board.classList.remove("shake_animation");
setTimeout(()=>{
board.classList.add("shake_animation");
}, 1)
},
(ev) => {
svg.fill(this.shakeButton, this.props.theme.virtualButtonUp);
this.board.bus.queue(DAL.MICROBIT_ID_GESTURE, 11); // GESTURE_SHAKE
}
)
// let shakeText = svg.child(shake.outer, "text", { x: 280, y: MB_HEIGHT - 5, class: "sim-text big inverted centered" }) as SVGTextElement;
// shakeText.textContent = "SHAKE"
}
}
private updateMicrophone() {
const b = board();
if (!b || !b.microphoneState.sensorUsed) return;
this.updateSoundLevel();
}
private updateButtonAB() {
let state = this.board;
if (state.buttonPairState.usesButtonAB && (this.buttons[2]).style.visibility != "visible") {
(this.buttonsOuter[2]).style.visibility = "visible";
(this.buttons[2]).style.visibility = "visible";
this.updateTheme();
}
}
private updateRSSI() {
let state = this.board;
if (!state) return;
const v = state.radioState.datagram.rssi;
if (v === undefined) return;
if (!this.rssi) {
let ax = 380;
let dax = 18;
let ayt = 10;
let ayb = 40;
const wh = dax * 5;
for (let i = 0; i < 4; ++i)
svg.child(this.g, "rect", { x: ax - 90 + i * 6, y: ayt + 28 - i * 4, width: 4, height: 2 + i * 4, fill: "#fff" })
this.rssi = svg.child(this.g, "text", { x: ax - 64, y: ayb, class: "sim-text" }) as SVGTextElement;
this.rssi.textContent = "";
}
const vt = v.toString();
if (vt !== this.rssi.textContent) {
this.rssi.textContent = v.toString();
this.antenna.setAttribute("aria-valuenow", this.rssi.textContent);
accessibility.setLiveContent(this.rssi.textContent);
}
}
private updatePin(pin: Pin, index: number) {
if (!pin) return;
let text = this.pinTexts[pin.id];
let v = "";
if (pin.mode & PinFlags.Analog) {
v = Math.floor(100 - (pin.value || 0) / 1023 * 100) + "%";
if (text) text.textContent = (pin.period ? "~" : "") + (pin.value || 0) + "";
}
else if (pin.mode & PinFlags.Digital) {
v = pin.value > 0 ? "0%" : "100%";
if (text) text.textContent = pin.value > 0 ? "1" : "0";
}
else if (pin.mode & PinFlags.Touch) {
v = pin.touched ? "0%" : "100%";
if (text) text.textContent = v;
} else {
v = "100%";
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", pin.mode & PinFlags.Analog ? "1023" : "100");
this.pins[index].setAttribute("aria-orientation", "vertical");
this.pins[index].setAttribute("aria-valuenow", text ? text.textContent : v);
accessibility.setLiveContent(text ? text.textContent : v);
}
}
private updateTemperature() {
let state = this.board;
if (!state || !state.thermometerState.usesTemperature) return;
let tmin = -5;
let tmax = 50;
if (!this.thermometer) {
let gid = "gradient-thermometer";
this.thermometerGradient = svg.linearGradient(this.defs, gid);
const ty = MB_HEIGHT - 270;
this.thermometer = svg.child(this.g, "rect", {
class: "sim-thermometer",
x: 0,
y: ty,
width: 30,
height: 160,
rx: 5,
ry: 5,
fill: `url(#${gid})`
});
this.thermometerText = svg.child(this.g, "text", {
class: 'sim-text big inverted centered',
x: 15,
y: ty + 190
}) as SVGTextElement;
this.updateTheme();
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, (cur.y - ty) / 160))
state.thermometerState.temperature = Math.floor(tmax - t * (tmax - tmin));
this.updateTemperature();
},
// 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 < tmin) state.thermometerState.temperature = tmin;
this.updateTemperature();
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
state.thermometerState.temperature++
if(state.thermometerState.temperature > tmax) state.thermometerState.temperature = tmax;
this.updateTemperature();
}
})
}
accessibility.makeFocusable(this.thermometer);
accessibility.setAria(this.thermometer, "slider", pxsim.localization.lf("Temperature Level"));
this.thermometer.setAttribute("aria-valuemin", tmin + "");
this.thermometer.setAttribute("aria-valuemax", tmax + "");
this.thermometer.setAttribute("aria-orientation", "vertical");
this.thermometer.setAttribute("aria-valuenow", state.thermometerState.temperature + "");
this.thermometer.setAttribute("aria-valuetext", state.thermometerState.temperature + "");
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 updateSoundLevel() {
let state = this.board;
if (!state || !state.microphoneState.sensorUsed) return;
const tmin = 0 // state.microphoneState.min;
const tmax = 255 //state.microphoneState.max;
if (!this.soundLevel) {
const level = state.microphoneState.getLevel();
let gid = "gradient-soundlevel";
this.soundLevelGradient = svg.linearGradient(this.defs, gid);
const ty = MB_HEIGHT - 270;
this.soundLevel = svg.child(this.g, "rect", {
class: "sim-thermometer",
x: 490,
y: ty,
width: 30,
height: 160,
rx: 5,
ry: 5,
fill: `url(#${gid})`
});
this.soundLevelText = svg.child(this.g, "text", {
class: 'sim-text big inverted centered',
x: 505,
y: ty + 190
}) as SVGTextElement;
this.soundLevelIcon = svg.child(this.g, "svg", {
x: 495,
y: 400,
viewbox: "0 0 20 29",
role: "img",
}) as SVGTextElement;
this.soundLevelIcon.setAttribute("aria-hidden", "true");
this.soundLevelIcon.setAttribute("focusable", "false");
this.soundLevelIcon.setAttribute("style", "pointer-events: none; opacity: 0.8; width: 20px;");
svg.child(this.soundLevelIcon, "path", {
fill: "white",
d: "M 10 19.9375 C 13.011719 19.9375 15.453125 17.503906 15.453125 14.5 L 15.453125 5.4375 C 15.453125 2.433594 13.011719 0 10 0 C 6.988281 0 4.546875 2.433594 4.546875 5.4375 L 4.546875 14.5 C 4.546875 17.503906 6.988281 19.9375 10 19.9375 Z M 19.089844 10.875 L 18.183594 10.875 C 17.679688 10.875 17.273438 11.28125 17.273438 11.78125 L 17.273438 14.5 C 17.273438 18.738281 13.609375 22.136719 9.273438 21.714844 C 5.496094 21.347656 2.726562 17.960938 2.726562 14.175781 L 2.726562 11.78125 C 2.726562 11.28125 2.320312 10.875 1.816406 10.875 L 0.910156 10.875 C 0.40625 10.875 0 11.28125 0 11.78125 L 0 14.054688 C 0 19.132812 3.632812 23.660156 8.636719 24.347656 L 8.636719 26.28125 L 5.453125 26.28125 C 4.953125 26.28125 4.546875 26.6875 4.546875 27.1875 L 4.546875 28.09375 C 4.546875 28.59375 4.953125 29 5.453125 29 L 14.546875 29 C 15.046875 29 15.453125 28.59375 15.453125 28.09375 L 15.453125 27.1875 C 15.453125 26.6875 15.046875 26.28125 14.546875 26.28125 L 11.363281 26.28125 L 11.363281 24.367188 C 16.234375 23.703125 20 19.535156 20 14.5 L 20 11.78125 C 20 11.28125 19.59375 10.875 19.089844 10.875 Z M 19.089844 10.875 "
});
if (this.props.runtime)
this.props.runtime.environmentGlobals[pxsim.localization.lf("sound level")] = state.microphoneState.getLevel();
this.updateTheme();
let pt = this.element.createSVGPoint();
svg.buttonEvents(this.soundLevel,
// move
(ev) => {
let cur = svg.cursorPoint(pt, this.element, ev);
let t = Math.max(0, Math.min(1, (cur.y - ty) / 160)) * tmax
console.log(tmax - t);
state.microphoneState.setLevel( Math.floor(tmax - t));
// state.microphoneState.setLevel(Math.floor(tmin + t * (tmax - tmin)));
this.updateMicrophone();
},
// 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.microphoneState.setLevel(state.microphoneState.getLevel() - 1);
if(state.microphoneState.getLevel() < tmin) state.microphoneState.setLevel(tmin);
this.updateMicrophone();
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
state.microphoneState.setLevel(state.microphoneState.getLevel() + 1);
if(state.microphoneState.getLevel() > tmax) state.microphoneState.setLevel(tmax);
this.updateMicrophone();
}
})
accessibility.makeFocusable(this.soundLevel);
accessibility.setAria(this.soundLevel, "slider", pxsim.localization.lf("Sound Level"));
this.soundLevel.setAttribute("aria-valuemin", tmin + "");
this.soundLevel.setAttribute("aria-valuemax", tmax + "");
this.soundLevel.setAttribute("aria-orientation", "vertical");
this.soundLevel.setAttribute("aria-valuenow", level + "");
this.soundLevel.setAttribute("aria-valuetext", level + "");
}
let t = Math.max(tmin, Math.min(tmax, state.microphoneState.getLevel()))
let per = Math.floor((state.microphoneState.getLevel() - tmin) / (tmax - tmin) * 100)
svg.setGradientValue(this.soundLevelGradient, (100 - per) + "%");
this.soundLevelText.textContent = t + "";
this.soundLevel.setAttribute("aria-valuenow", t.toString());
this.soundLevel.setAttribute("aria-valuetext", t + "");
accessibility.setLiveContent(t + "");
}
private updateHeading() {
const valMin = 0;
const valMax = 360;
let xc = 501.2;
let yc = 75;
let state = this.board;
if (!state || !state.compassState.usesHeading) return;
// /*
if (!this.headInitialized) {
let p = this.heads[1];
svg.child(p, "circle", {style: "fill:#DDDDDD55;stroke:#3A3A3A;", cx: "501.2", cy: "75", r: "55" });
svg.child(p, "polyline", {style: "fill:#008EEF;stroke:#3A3A3A;", points: "517.7,75 501.1,140.2 484.6,75" });
svg.child(p, "polyline", {style: "fill:#FF3951;stroke:#3A3A3A;", points: "484.6,75 501.1,9.5 517.7,75" });
svg.child(p, "circle", {style: "fill:#748476;stroke:#3A3A3A;", cx: "501.1", cy: "75", r: "16.5" });
svg.child(p, "circle", {style: "fill:#CCDBCE;", cx: "501.1", cy: "75", r: "10" });
// p.setAttribute("d", "m269.9,50.134647l0,0l-39.5,0l0,0c-14.1,0.1 -24.6,10.7 -24.6,24.8c0,13.9 10.4,24.4 24.3,24.7l0,0l39.6,0c14.2,0 40.36034,-22.97069 40.36034,-24.85394c0,-1.88326 -26.06034,-24.54606 -40.16034,-24.64606m-0.2,39l0,0l-39.3,0c-7.7,-0.1 -14,-6.4 -14,-14.2c0,-7.8 6.4,-14.2 14.2,-14.2l39.1,0c7.8,0 14.2,6.4 14.2,14.2c0,7.9 -6.4,14.2 -14.2,14.2l0,0l0,0z");
this.updateTheme();
let pt = this.element.createSVGPoint();
svg.buttonEvents(this.head,
// move
(ev: MouseEvent) => {
let cur = svg.cursorPoint(pt, this.element, ev);
state.compassState.heading = valMax - (Math.floor(Math.atan2(cur.y - yc, cur.x - xc) * 180 / Math.PI) + 90) - valMax;
if (state.compassState.heading < valMin) state.compassState.heading += valMax;
this.updateHeading();
},
// 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.compassState.heading--;
if(state.compassState.heading < valMin) state.compassState.heading += valMax;
this.updateHeading();
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
state.compassState.heading++;
if(state.compassState.heading >= valMax) state.compassState.heading -= valMax;;
this.updateHeading();
}
});
this.headInitialized = true;
}
accessibility.makeFocusable(this.head);
accessibility.setAria(this.head, "slider", pxsim.localization.lf("Heading"));
this.head.setAttribute("aria-valuemin", valMin + "");
this.head.setAttribute("aria-valuemax", valMax + "");
this.head.setAttribute("aria-orientation", "vertical");
this.head.setAttribute("aria-valuenow", state.compassState.heading + "");
this.head.setAttribute("aria-valuetext", state.compassState.heading + "");
let txt = state.compassState.heading.toString() + "°";
if (txt != this.headText.textContent) {
svg.rotateElement(this.head, xc, yc, valMax - state.compassState.heading - 90);
this.headText.textContent = txt;
}
}
private lastFlashTime: number = 0;
public flashSystemLed() {
if (!this.systemLed)
this.systemLed = svg.child(this.g, "circle", { class: "sim-systemled", cx: 160.8, cy: 150.9, r: 4 })
let now = Date.now();
if (now - this.lastFlashTime > 150) {
this.lastFlashTime = now;
svg.animate(this.systemLed, "sim-flash")
}
}
private lastAntennaFlash: number = 0;
public flashAntenna() {
if (!this.antenna) {
let ax = 480;
let dax = 18;
let ayt = 10;
let ayb = 40;
this.antenna = svg.child(this.g, "polyline", { class: "sim-antenna", points: `${ax},${ayb} ${ax},${ayt} ${ax += dax},${ayt} ${ax},${ayb} ${ax += dax},${ayb} ${ax},${ayt} ${ax += dax},${ayt} ${ax},${ayb} ${ax += dax},${ayb} ${ax},${ayt} ${ax += dax},${ayt}` })
}
let now = Date.now();
if (now - this.lastAntennaFlash > 200) {
this.lastAntennaFlash = now;
svg.animate(this.antenna, 'sim-flash-stroke')
}
}
private updatePins() {
let state = this.board;
if (!state) return;
state.edgeConnectorState.pins.forEach((pin, i) => this.updatePin(pin, i));
}
private updateLightLevel() {
let state = this.board;
const valMin = 0;
const valMax = 255;
if (!state || !state.lightSensorState.usesLightLevel) return;
if (!this.lightLevelButton) {
let gid = "gradient-light-level";
this.lightLevelGradient = svg.linearGradient(this.defs, gid)
const cx = 25;
const cy = 75;
const r = 55;
this.lightLevelButton = svg.child(this.g, "circle", {
cx: `${cx}px`, cy: `${cy}px`, r: `${r}px`,
class: 'sim-light-level-button',
fill: `url(#${gid})`
}) 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;
let level = valMax - Math.max(valMin, Math.min(valMax, Math.floor((pos.y - (cy - r)) / (2 * r) * valMax)));
if (level != state.lightSensorState.lightLevel) {
state.lightSensorState.lightLevel = level;
this.applyLightLevel();
}
},
// 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.lightSensorState.lightLevel--;
if(state.lightSensorState.lightLevel < valMin) state.lightSensorState.lightLevel = valMin;
this.applyLightLevel();
} else if (charCode === 38 || charCode === 39) { // Up/Right arrow
state.lightSensorState.lightLevel++
if(state.lightSensorState.lightLevel > valMax) state.lightSensorState.lightLevel = valMax;
this.applyLightLevel();
}
})
this.lightLevelText = svg.child(this.g, "text", { x: cx , y: cy + r + 35, text: '', class: 'sim-text inverted big centered' }) as SVGTextElement;
this.updateTheme();
}
accessibility.makeFocusable(this.lightLevelButton);
accessibility.setAria(this.lightLevelButton, "slider", pxsim.localization.lf("Light Level"));
this.lightLevelButton.setAttribute("aria-valuemin", valMin + "");
this.lightLevelButton.setAttribute("aria-valuemax", valMax + "");
this.lightLevelButton.setAttribute("aria-orientation", "vertical");
this.lightLevelButton.setAttribute("aria-valuenow", state.lightSensorState.lightLevel + "");
this.lightLevelButton.setAttribute("aria-valuetext", state.lightSensorState.lightLevel + "");
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor((255 - state.lightSensorState.lightLevel) * 100 / 255))) + '%')
this.lightLevelText.textContent = state.lightSensorState.lightLevel.toString();
}
private applyLightLevel() {
let lv = this.board.lightSensorState.lightLevel;
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor((255 - lv) * 100 / 255))) + '%')
this.lightLevelText.textContent = lv.toString();
}
private updateTilt() {
return;
if (this.props.disableTilt) return;
let state = this.board;
if (!state || !state.accelerometerState.accelerometer.isActive) return;
const x = state.accelerometerState.accelerometer.getX();
const y = -state.accelerometerState.accelerometer.getY();
const af = 8 / 1023;
const s = 1 - Math.min(0.1, Math.pow(Math.max(Math.abs(x), Math.abs(y)) / 1023, 2) / 35);
this.element.style.transform = `perspective(30em) rotateX(${y * af}deg) rotateY(${x * af}deg) scale(${s}, ${s})`
this.element.style.perspectiveOrigin = "50% 50% 50%";
this.element.style.perspective = "30em";
}
private buildDom() {
this.element = new DOMParser().parseFromString(BOARD_SVG, "image/svg+xml").querySelector("svg") as SVGSVGElement;
svg.hydrate(this.element, {
"version": "1.0",
"viewBox": `0 0 ${MB_WIDTH} ${MB_HEIGHT}`,
"class": "sim",
"x": "0px",
"y": "0px",
"width": MB_WIDTH + "px",
"height": MB_HEIGHT + "px",
"fill": "rgba(0,0,0,0)"
});
this.style = svg.child(this.element, "style", {});
this.style.textContent = MB_STYLE;
this.defs = svg.child(this.element, "defs", {});
this.g = svg.elt("g");
this.element.appendChild(this.g);
// filters
let ledglow = svg.child(this.defs, "filter", { id: "ledglow", x: "-75%", y: "-75%", width: "300%", height: "300%" });
svg.child(ledglow, "feMorphology", { operator: "dilate", radius: "4", in: "SourceAlpha", result: "thicken" });
svg.child(ledglow, "feGaussianBlur", { stdDeviation: "5", in: "thicken", result: "blurred" });
svg.child(ledglow, "feFlood", { "flood-color": "rgb(255, 17, 77)", result: "glowColor" });
svg.child(ledglow, "feComposite", { in: "glowColor", in2: "blurred", operator: "in", result: "ledglow_colored" });
let ledglowMerge = svg.child(ledglow, "feMerge", {});
svg.child(ledglowMerge, "feMergeNode", { in: "ledglow_colored" });
svg.child(ledglowMerge, "feMergeNode", { in: "SourceGraphic" });
let glow = svg.child(this.defs, "filter", { id: "filterglow", x: "-5%", y: "-5%", width: "120%", height: "120%" });
svg.child(glow, "feGaussianBlur", { stdDeviation: "5", result: "glow" });
let merge = svg.child(glow, "feMerge", {});
for (let i = 0; i < 3; ++i) svg.child(merge, "feMergeNode", { in: "glow" })
// leds
this.leds = [];
this.ledsOuter = [];
const left = Number(this.element.getElementById("LED_0_0").getAttribute("x"));
const top = Number(this.element.getElementById("LED_0_0").getAttribute("y"));
const ledoffw = Number(this.element.getElementById("LED_1_0").getAttribute("x"))-left;
const ledoffh = Number(this.element.getElementById("LED_0_1").getAttribute("y"))-top;
// const ledw = 5.1;
// const ledh = 12.9;
for (let i = 0; i < 5; ++i) {
let ledtop = i * ledoffh + top;
for (let j = 0; j < 5; ++j) {
let ledleft = j * ledoffw + left;
let k = i * 5 + j;
this.ledsOuter.push(svg.child(this.g, "rect", { class: "sim-led-back", x: ledleft, y: ledtop, width: 10, height: 20, rx: 2, ry: 2 }));
let led = svg.child(this.g, "rect", { class: "sim-led", x: ledleft - 2, y: ledtop - 2, width: 14, height: 24, rx: 3, ry: 3, title: `(${j},${i})` });
svg.filter(led, `url(#ledglow)`)
this.leds.push(led);
}
}
// head
// this.headg = svg.child(this.g, "g", { style: "transform: translate(100px, 0px);" });
this.head = svg.child(this.g, "g", { class: "sim-head" });
svg.child(this.head, "circle", { cx: 501.2, cy: 75, r: 100, fill: "transparent" })
this.headParts = svg.child(this.head, "g", { class: "sim-button-outer sim-button-group" });
this.heads = []
// background
this.heads.push(svg.path(this.headParts, "sim-button",""));
// shapes
this.heads.push(svg.child(this.headParts, "g", { class: "sim-theme" }));
// this.heads.push(svg.path(this.headParts, "sim-theme", "M230.6,69.7c-2.9,0-5.3,2.4-5.3,5.3c0,2.9,2.4,5.3,5.3,5.3c2.9,0,5.3-2.4,5.3-5.3C235.9,72.1,233.5,69.7,230.6,69.7"));
// this.heads.push(svg.path(this.headParts, "sim-theme", "M269.7,80.3c2.9,0,5.3-2.4,5.3-5.3c0-2.9-2.4-5.3-5.3-5.3c-2.9,0-5.3,2.4-5.3,5.3C264.4,77.9,266.8,80.3,269.7,80.3"));
this.headText = svg.child(this.g, "text", { x: 500, y: 165, class: "sim-text inverted big centered" })
// https://www.microbit.co.uk/device/pins
// P0, P1, P2
this.pins = pinNames.map(n => {
let p = this.element.getElementById(n) as SVGElement;
if(!p) console.log("missing "+n);
U.addClass(p, "sim-pin");
// console.log(p);
return p;
});
this.pins.forEach((p, i) => svg.hydrate(p, { title: pinTitles[i] }));
this.pinGradients = this.pins.map((pin, i) => {
let gid = "gradient-pin-" + i
let lg = svg.linearGradient(this.defs, gid)
pin.setAttribute("fill", `url(#${gid})`);
return lg;
})
// this.pinTexts = [
// [-20, 340],
// [50, 495],
// [450, 495],
// [500, 340]
// ].map(p => svg.child(this.g, "text", { class: "sim-text-pin", x: p[0], y: p[1] }));
this.pinTexts = {
[DigitalPin.P0]: svg.child(this.g, "text", { class: "sim-text-pin big centered", x: 20, y: 325 }),
[DigitalPin.P1]: svg.child(this.g, "text", { class: "sim-text-pin big centered", x: 135, y: 540 }),
[DigitalPin.P2]: svg.child(this.g, "text", { class: "sim-text-pin big centered", x: 395, y: 540 }),
[DigitalPin.P3]: svg.child(this.g, "text", { class: "sim-text-pin big centered", x: 540, y: 325 })
}
// BTN A, B
const btnids = ["BTN_A", "BTN_B"];
this.buttonsOuter = btnids.map(n => this.element.getElementById(n + "_BOX") as SVGElement);
this.buttonsOuter.forEach(b => U.addClass(b, "sim-button-outer"));
this.buttons = btnids.map(n => this.element.getElementById(n) as SVGElement);
this.buttons.forEach(b => U.addClass(b, "sim-button"));
// BTN A+B
const outerBtn = (left: number, top: number) => {
const button = this.mkBtn(left, top, 'A + B');
this.buttonsOuter.push(button.outer);
this.buttons.push(button.inner);
return button;
}
let ab = outerBtn(100, MB_HEIGHT - 75);
// let abtext = svg.child(ab.outer, "text", { x: 210, y: MB_HEIGHT - 5, class: "sim-text big inverted centered" }) as SVGTextElement;
// abtext.textContent = "A+B";
(this.buttonsOuter[2]).style.visibility = "hidden";
(this.buttons[2]).style.visibility = "hidden";
}
private mkBtn(left: number, top: number, text: string): { outer: SVGElement, inner: SVGElement } {
const btnr = 2;
const btnw = 20;
const btnn = 1.6;
const btnnm = 2;
const btnb = 5;
let btng = svg.child(this.g, "g", { class: "sim-button-group" });
// var fo = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
var fo = svg.child(btng, "foreignObject");
fo.setAttribute("id", "y");
fo.setAttribute("x", left+'');
fo.setAttribute("y", top+'');
fo.setAttribute("width", "300");
fo.setAttribute("height", "100");
fo.innerHTML = `
`;
// var ta = document.createElement("button");
// ta.innerText = text;
// fo.appendChild(ta);
// 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 });
// svg.child(btng, "circle", { class: "sim-button-nut", cx: left + btnnm, cy: top + btnw - btnnm, r: btnn });
// svg.child(btng, "circle", { class: "sim-button-nut", cx: left + btnw - btnnm, cy: top + btnw - btnnm, r: btnn });
// svg.child(btng, "circle", { class: "sim-button-nut", cx: left + btnw - btnnm, cy: top + btnnm, r: btnn });
const outer = btng;
const inner = svg.child(btng, "circle", {
class: "sim-button",
cx: left + btnw / 2,
cy: top + btnw / 2,
r: 0
});
return { outer, inner };
}
private attachEvents() {
this.attachIFrameEvents();
this.attachAccelerometerEvents();
this.attachPinsIOEvents();
this.attachPinsTouchEvents();
this.attachABEvents();
this.attachAPlusBEvents();
}
private attachIFrameEvents() {
Runtime.messagePosted = (msg) => {
switch (msg.type || "") {
case "serial": this.flashSystemLed(); break;
case "radiopacket": this.flashAntenna(); break;
case "eventbus":
if ((msg).id == DAL.MES_BROADCAST_GENERAL_ID)
this.flashAntenna();
break;
}
}
}
private attachAccelerometerEvents() {
let tiltDecayer: any = undefined;
this.element.addEventListener(pointerEvents.move, (ev: MouseEvent) => {
const state = this.board;
if (!state.accelerometerState.accelerometer.isActive) return;
if (tiltDecayer) {
clearInterval(tiltDecayer);
tiltDecayer = 0;
}
const bbox = this.element.getBoundingClientRect();
// ev.clientX and ev.clientY are not defined on mobile iOS
const xPos = ev.clientX != null ? ev.clientX : ev.pageX;
const yPos = ev.clientY != null ? ev.clientY : ev.pageY;
const ax = (xPos - bbox.width / 2) / (bbox.width / 3);
const ay = (yPos - bbox.height / 2) / (bbox.height / 3);
const x = - Math.max(- 1023, Math.min(1023, Math.floor(ax * 1023)));
const y = - Math.max(- 1023, Math.min(1023, Math.floor(ay * 1023)));
const z2 = 1023 * 1023 - x * x - y * y;
const z = Math.floor((z2 > 0 ? -1 : 1) * Math.sqrt(Math.abs(z2)));
state.accelerometerState.accelerometer.update(x, y, z);
this.updateTilt();
}, false);
this.element.addEventListener(pointerEvents.leave, (ev: MouseEvent) => {
let state = this.board;
if (!state.accelerometerState.accelerometer.isActive) return;
if (!tiltDecayer) {
tiltDecayer = setInterval(() => {
let accx = state.accelerometerState.accelerometer.getX(MicroBitCoordinateSystem.RAW);
accx = Math.floor(Math.abs(accx) * 0.85) * (accx > 0 ? 1 : -1);
let accy = state.accelerometerState.accelerometer.getY(MicroBitCoordinateSystem.RAW);
accy = Math.floor(Math.abs(accy) * 0.85) * (accy > 0 ? 1 : -1);
let accz = -Math.sqrt(Math.max(0, 1023 * 1023 - accx * accx - accy * accy));
if (Math.abs(accx) <= 24 && Math.abs(accy) <= 24) {
clearInterval(tiltDecayer);
tiltDecayer = 0;
accx = 0;
accy = 0;
accz = -1023;
}
state.accelerometerState.accelerometer.update(accx, accy, accz);
this.updateTilt();
}, 50)
}
}, false);
}
private attachPinsIOEvents() {
this.pins.slice(0, 4).forEach((pin, index) => {
// var index = i + 2;
if (!this.board.edgeConnectorState.pins[index]) return;
let pt = this.element.createSVGPoint();
let xpos = (index === 0 || index === 3) ? 300 : 520;
let vMax = (index === 0 || index === 3) ? 1 : 1023;
svg.buttonEvents(pin,
// move
ev => {
let state = this.board;
let pin = state.edgeConnectorState.pins[index];
let svgpin = this.pins[index];
if (pin.mode & PinFlags.Input) {
let cursor = svg.cursorPoint(pt, this.element, ev);
let v = (xpos - cursor.y) / 70 * (vMax + 1);
pin.value = Math.max(0, Math.min(vMax, Math.floor(v)));
}
this.updatePin(pin, index);
},
// start
ev => {
let state = this.board;
let pin = state.edgeConnectorState.pins[index];
let svgpin = this.pins[index];
U.addClass(svgpin, "touched");
if (pin.mode & PinFlags.Input) {
let cursor = svg.cursorPoint(pt, this.element, ev);
let v = (xpos - cursor.y) / 70 * (vMax + 1);
pin.value = Math.max(0, Math.min(vMax, Math.floor(v)));
}
this.updatePin(pin, index);
},
// stop
(ev: MouseEvent) => {
let state = this.board;
let pin = state.edgeConnectorState.pins[index];
let svgpin = this.pins[index];
U.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);
}
});
})
}
private attachPinsTouchEvents() {
this.pins.slice(0, 4).forEach((btn, i) => {
var index = i;
let state = this.board;
let pressedTime: number;
pointerEvents.down.forEach(evid => btn.addEventListener(evid, ev => {
let state = this.board;
// console.log(`down ${state.edgeConnectorState.pins[i].id}`)
state.edgeConnectorState.pins[i].touched = true;
this.updatePin(state.edgeConnectorState.pins[i], index);
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_DOWN);
pressedTime = runtime.runningTime()
}));
// btn.addEventListener(pointerEvents.leave, ev => {
// let state = this.board;
// state.edgeConnectorState.pins[i].touched = false;
// this.updatePin(state.edgeConnectorState.pins[i], index);
// })
btn.addEventListener(pointerEvents.up, ev => {
let state = this.board;
// console.log(`up ${state.edgeConnectorState.pins[i].id}, index ${index}`)
state.edgeConnectorState.pins[i].touched = false;
this.updatePin(state.edgeConnectorState.pins[i], index);
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_UP);
const currentTime = runtime.runningTime()
if (currentTime - pressedTime > DAL.DEVICE_BUTTON_LONG_CLICK_TIME) {
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_LONG_CLICK);
// console.log(`& long click`)
} else {
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
// console.log(`& click`)
}
pressedTime = undefined;
})
accessibility.enableKeyboardInteraction(btn, undefined, () => {
let state = this.board;
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_DOWN);
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_UP);
this.board.bus.queue(state.edgeConnectorState.pins[i].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
});
})
}
private attachABEvents() {
const bpState = this.board.buttonPairState;
const stateButtons: Button[] = [bpState.aBtn, bpState.bBtn];
const elButtonOuters = this.buttonsOuter.slice(0,2);
const elButtons = this.buttons.slice(0,2);
elButtonOuters.forEach((btn, index) => {
let pressedTime: number;
pointerEvents.down.forEach(evid => btn.addEventListener(evid, ev => {
// console.log(`down ${stateButtons[index].id}`)
stateButtons[index].pressed = true;
svg.fill(elButtons[index], this.props.theme.buttonDown);
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_DOWN);
pressedTime = runtime.runningTime()
}));
btn.addEventListener(pointerEvents.leave, ev => {
stateButtons[index].pressed = false;
svg.fill(elButtons[index], this.props.theme.buttonUps[0]);
})
btn.addEventListener(pointerEvents.up, ev => {
stateButtons[index].pressed = false;
svg.fill(elButtons[index], this.props.theme.buttonUps[0]);
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
const currentTime = runtime.runningTime()
if (currentTime - pressedTime > DAL.DEVICE_BUTTON_LONG_CLICK_TIME)
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_LONG_CLICK);
else
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
pressedTime = undefined;
})
accessibility.enableKeyboardInteraction(btn, undefined, () => {
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_DOWN);
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
});
})
}
private attachAPlusBEvents() {
const bpState = this.board.buttonPairState;
const stateButtons: Button[] = [bpState.aBtn, bpState.bBtn];
let pressedTime: number;
// A+B
pointerEvents.down.forEach(evid => this.buttonsOuter[2].addEventListener(evid, ev => {
bpState.aBtn.pressed = true;
bpState.bBtn.pressed = true;
bpState.abBtn.pressed = true;
svg.fill(this.buttons[0], this.props.theme.buttonDown);
svg.fill(this.buttons[1], this.props.theme.buttonDown);
svg.fill(this.buttons[2], this.props.theme.buttonDown);
this.board.bus.queue(stateButtons[0].id, DAL.MICROBIT_BUTTON_EVT_DOWN);
this.board.bus.queue(stateButtons[1].id, DAL.MICROBIT_BUTTON_EVT_DOWN);
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_DOWN);
pressedTime = runtime.runningTime()
}));
this.buttonsOuter[2].addEventListener(pointerEvents.leave, ev => {
bpState.aBtn.pressed = false;
bpState.bBtn.pressed = false;
bpState.abBtn.pressed = false;
svg.fill(this.buttons[0], this.props.theme.buttonUps[0]);
svg.fill(this.buttons[1], this.props.theme.buttonUps[1]);
svg.fill(this.buttons[2], this.props.theme.virtualButtonUp);
})
this.buttonsOuter[2].addEventListener(pointerEvents.up, ev => {
bpState.aBtn.pressed = false;
bpState.bBtn.pressed = false;
bpState.abBtn.pressed = false;
svg.fill(this.buttons[0], this.props.theme.buttonUps[0]);
svg.fill(this.buttons[1], this.props.theme.buttonUps[1]);
svg.fill(this.buttons[2], this.props.theme.virtualButtonUp);
this.board.bus.queue(stateButtons[0].id, DAL.MICROBIT_BUTTON_EVT_UP);
this.board.bus.queue(stateButtons[1].id, DAL.MICROBIT_BUTTON_EVT_UP);
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_UP);
const currentTime = runtime.runningTime()
if (currentTime - pressedTime > DAL.DEVICE_BUTTON_LONG_CLICK_TIME)
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_LONG_CLICK);
else
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_CLICK);
pressedTime = undefined;
})
accessibility.enableKeyboardInteraction(this.buttonsOuter[2], undefined, () => {
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_DOWN);
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_UP);
this.board.bus.queue(bpState.abBtn.id, DAL.MICROBIT_BUTTON_EVT_CLICK);
});
}
}
}