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); }); } } }