refacotring various simulator features into pxt
This commit is contained in:
parent
33f12f9ecc
commit
910772d54e
@ -29,7 +29,13 @@ namespace pxsim {
|
|||||||
|
|
||||||
// components
|
// components
|
||||||
this.ledMatrixState = new LedMatrixState(runtime);
|
this.ledMatrixState = new LedMatrixState(runtime);
|
||||||
this.buttonPairState = new ButtonPairState();
|
this.buttonPairState = new ButtonPairState({
|
||||||
|
ID_BUTTON_A: DAL.MICROBIT_ID_BUTTON_A,
|
||||||
|
ID_BUTTON_B: DAL.MICROBIT_ID_BUTTON_B,
|
||||||
|
ID_BUTTON_AB: DAL.MICROBIT_ID_BUTTON_AB,
|
||||||
|
BUTTON_EVT_UP: DAL.MICROBIT_BUTTON_EVT_UP,
|
||||||
|
BUTTON_EVT_CLICK: DAL.MICROBIT_BUTTON_EVT_CLICK
|
||||||
|
});
|
||||||
this.edgeConnectorState = new EdgeConnectorState();
|
this.edgeConnectorState = new EdgeConnectorState();
|
||||||
this.radioState = new RadioState(runtime);
|
this.radioState = new RadioState(runtime);
|
||||||
this.accelerometerState = new AccelerometerState(runtime);
|
this.accelerometerState = new AccelerometerState(runtime);
|
||||||
|
@ -226,25 +226,6 @@ namespace pxsim.visuals {
|
|||||||
wireframe?: boolean;
|
wireframe?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPointerEvents {
|
|
||||||
up: string,
|
|
||||||
down: string,
|
|
||||||
move: string,
|
|
||||||
leave: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pointerEvents = !!(window as any).PointerEvent ? {
|
|
||||||
up: "pointerup",
|
|
||||||
down: "pointerdown",
|
|
||||||
move: "pointermove",
|
|
||||||
leave: "pointerleave"
|
|
||||||
} : {
|
|
||||||
up: "mouseup",
|
|
||||||
down: "mousedown",
|
|
||||||
move: "mousemove",
|
|
||||||
leave: "mouseleave"
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MicrobitBoardSvg implements BoardView {
|
export class MicrobitBoardSvg implements BoardView {
|
||||||
public element: SVGSVGElement;
|
public element: SVGSVGElement;
|
||||||
private style: SVGStyleElement;
|
private style: SVGStyleElement;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
namespace pxsim.input {
|
namespace pxsim.input {
|
||||||
export function onButtonPressed(button: number, handler: RefAction): void {
|
export function onButtonPressed(button: number, handler: RefAction): void {
|
||||||
let b = board().buttonPairState;
|
let b = board().buttonPairState;
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
if (button == b.props.ID_BUTTON_AB && !b.usesButtonAB) {
|
||||||
b.usesButtonAB = true;
|
b.usesButtonAB = true;
|
||||||
runtime.queueDisplayUpdate();
|
runtime.queueDisplayUpdate();
|
||||||
}
|
}
|
||||||
@ -10,32 +10,12 @@ namespace pxsim.input {
|
|||||||
|
|
||||||
export function buttonIsPressed(button: number): boolean {
|
export function buttonIsPressed(button: number): boolean {
|
||||||
let b = board().buttonPairState;
|
let b = board().buttonPairState;
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
if (button == b.abBtn.id && !b.usesButtonAB) {
|
||||||
b.usesButtonAB = true;
|
b.usesButtonAB = true;
|
||||||
runtime.queueDisplayUpdate();
|
runtime.queueDisplayUpdate();
|
||||||
}
|
}
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_A) return b.aBtn.pressed;
|
if (button == b.aBtn.id) return b.aBtn.pressed;
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_B) return b.bBtn.pressed;
|
if (button == b.bBtn.id) return b.bBtn.pressed;
|
||||||
return b.abBtn.pressed || (b.aBtn.pressed && b.bBtn.pressed);
|
return b.abBtn.pressed || (b.aBtn.pressed && b.bBtn.pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace pxsim {
|
|
||||||
export class Button {
|
|
||||||
constructor(public id: number) { }
|
|
||||||
pressed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ButtonPairState {
|
|
||||||
usesButtonAB: boolean = false;
|
|
||||||
aBtn: Button;
|
|
||||||
bBtn: Button;
|
|
||||||
abBtn: Button;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.aBtn = new Button(DAL.MICROBIT_ID_BUTTON_A);
|
|
||||||
this.bBtn = new Button(DAL.MICROBIT_ID_BUTTON_B);
|
|
||||||
this.abBtn = new Button(DAL.MICROBIT_ID_BUTTON_AB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,10 +13,3 @@ namespace pxsim.input {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace pxsim {
|
|
||||||
export class CompassState {
|
|
||||||
usesHeading = false;
|
|
||||||
heading = 90;
|
|
||||||
}
|
|
||||||
}
|
|
@ -116,72 +116,6 @@ namespace pxsim {
|
|||||||
}
|
}
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnimationOptions {
|
|
||||||
interval: number;
|
|
||||||
// false means last frame
|
|
||||||
frame: () => boolean;
|
|
||||||
whenDone?: (cancelled: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AnimationQueue {
|
|
||||||
private queue: AnimationOptions[] = [];
|
|
||||||
private process: () => void;
|
|
||||||
|
|
||||||
constructor(private runtime: Runtime) {
|
|
||||||
this.process = () => {
|
|
||||||
let top = this.queue[0]
|
|
||||||
if (!top) return
|
|
||||||
if (this.runtime.dead) return
|
|
||||||
runtime = this.runtime
|
|
||||||
let res = top.frame()
|
|
||||||
runtime.queueDisplayUpdate()
|
|
||||||
runtime.maybeUpdateDisplay()
|
|
||||||
if (res === false) {
|
|
||||||
this.queue.shift();
|
|
||||||
// if there is already something in the queue, start processing
|
|
||||||
if (this.queue[0])
|
|
||||||
setTimeout(this.process, this.queue[0].interval)
|
|
||||||
// this may push additional stuff
|
|
||||||
top.whenDone(false);
|
|
||||||
} else {
|
|
||||||
setTimeout(this.process, top.interval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancelAll() {
|
|
||||||
let q = this.queue
|
|
||||||
this.queue = []
|
|
||||||
for (let a of q) {
|
|
||||||
a.whenDone(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancelCurrent() {
|
|
||||||
let top = this.queue[0]
|
|
||||||
if (top) {
|
|
||||||
this.queue.shift();
|
|
||||||
top.whenDone(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enqueue(anim: AnimationOptions) {
|
|
||||||
if (!anim.whenDone) anim.whenDone = () => { };
|
|
||||||
this.queue.push(anim)
|
|
||||||
// we start processing when the queue goes from 0 to 1
|
|
||||||
if (this.queue.length == 1)
|
|
||||||
this.process()
|
|
||||||
}
|
|
||||||
|
|
||||||
public executeAsync(anim: AnimationOptions) {
|
|
||||||
U.assert(!anim.whenDone)
|
|
||||||
return new Promise<boolean>((resolve, reject) => {
|
|
||||||
anim.whenDone = resolve
|
|
||||||
this.enqueue(anim)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace pxsim.images {
|
namespace pxsim.images {
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
namespace pxsim {
|
|
||||||
export class LightSensorState {
|
|
||||||
usesLightLevel = false;
|
|
||||||
lightLevel = 128;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace pxsim.input {
|
namespace pxsim.input {
|
||||||
export function lightLevel(): number {
|
export function lightLevel(): number {
|
||||||
let b = board().lightSensorState;
|
let b = board().lightSensorState;
|
||||||
|
@ -22,58 +22,6 @@ namespace pxsim {
|
|||||||
throw new Error("PANIC " + code)
|
throw new Error("PANIC " + code)
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace AudioContextManager {
|
|
||||||
let _context: any; // AudioContext
|
|
||||||
let _vco: any; // OscillatorNode;
|
|
||||||
let _vca: any; // GainNode;
|
|
||||||
|
|
||||||
function context(): any {
|
|
||||||
if (!_context) _context = freshContext();
|
|
||||||
return _context;
|
|
||||||
}
|
|
||||||
|
|
||||||
function freshContext(): any {
|
|
||||||
(<any>window).AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
|
||||||
if ((<any>window).AudioContext) {
|
|
||||||
try {
|
|
||||||
// this call my crash.
|
|
||||||
// SyntaxError: audio resources unavailable for AudioContext construction
|
|
||||||
return new (<any>window).AudioContext();
|
|
||||||
} catch (e) { }
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stop() {
|
|
||||||
if (_vca) _vca.gain.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tone(frequency: number, gain: number) {
|
|
||||||
if (frequency <= 0) return;
|
|
||||||
let ctx = context();
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
gain = Math.max(0, Math.min(1, gain));
|
|
||||||
if (!_vco) {
|
|
||||||
try {
|
|
||||||
_vco = ctx.createOscillator();
|
|
||||||
_vca = ctx.createGain();
|
|
||||||
_vco.connect(_vca);
|
|
||||||
_vca.connect(ctx.destination);
|
|
||||||
_vca.gain.value = gain;
|
|
||||||
_vco.start(0);
|
|
||||||
} catch (e) {
|
|
||||||
_vco = undefined;
|
|
||||||
_vca = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_vco.frequency.value = frequency;
|
|
||||||
_vca.gain.value = gain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RuntimeOptions {
|
export interface RuntimeOptions {
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
@ -1,204 +0,0 @@
|
|||||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
|
||||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
|
||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
|
||||||
|
|
||||||
namespace pxsim.visuals {
|
|
||||||
export function mkBtnSvg(xy: Coord): SVGAndSize<SVGGElement> {
|
|
||||||
let [innerCls, outerCls] = ["sim-button", "sim-button-outer"];
|
|
||||||
const tabSize = PIN_DIST / 2.5;
|
|
||||||
const pegR = PIN_DIST / 5;
|
|
||||||
const btnR = PIN_DIST * .8;
|
|
||||||
const pegMargin = PIN_DIST / 8;
|
|
||||||
const plateR = PIN_DIST / 12;
|
|
||||||
|
|
||||||
const pegOffset = pegMargin + pegR;
|
|
||||||
let [x, y] = xy;
|
|
||||||
const left = x - tabSize / 2;
|
|
||||||
const top = y - tabSize / 2;
|
|
||||||
const plateH = 3 * PIN_DIST - tabSize;
|
|
||||||
const plateW = 2 * PIN_DIST + tabSize;
|
|
||||||
const plateL = left;
|
|
||||||
const plateT = top + tabSize;
|
|
||||||
const btnCX = plateL + plateW / 2;
|
|
||||||
const btnCY = plateT + plateH / 2;
|
|
||||||
|
|
||||||
let btng = <SVGGElement>svg.elt("g");
|
|
||||||
//tabs
|
|
||||||
const mkTab = (x: number, y: number) => {
|
|
||||||
svg.child(btng, "rect", { class: "sim-button-tab", x: x, y: y, width: tabSize, height: tabSize})
|
|
||||||
}
|
|
||||||
mkTab(left, top);
|
|
||||||
mkTab(left + 2 * PIN_DIST, top);
|
|
||||||
mkTab(left, top + 3 * PIN_DIST);
|
|
||||||
mkTab(left + 2 * PIN_DIST, top + 3 * PIN_DIST);
|
|
||||||
|
|
||||||
//plate
|
|
||||||
svg.child(btng, "rect", { class: outerCls, x: plateL, y: plateT, rx: plateR, ry: plateR, width: plateW, height: plateH });
|
|
||||||
|
|
||||||
//pegs
|
|
||||||
const mkPeg = (x: number, y: number) => {
|
|
||||||
svg.child(btng, "circle", { class: "sim-button-nut", cx: x, cy: y, r: pegR });
|
|
||||||
}
|
|
||||||
mkPeg(plateL + pegOffset, plateT + pegOffset)
|
|
||||||
mkPeg(plateL + plateW - pegOffset, plateT + pegOffset)
|
|
||||||
mkPeg(plateL + pegOffset, plateT + plateH - pegOffset)
|
|
||||||
mkPeg(plateL + plateW - pegOffset, plateT + plateH - pegOffset)
|
|
||||||
|
|
||||||
//inner btn
|
|
||||||
let innerBtn = svg.child(btng, "circle", { class: innerCls, cx: btnCX, cy: btnCY, r: btnR });
|
|
||||||
|
|
||||||
//return
|
|
||||||
return { el: btng, y: top, x: left, w: plateW, h: plateH + 2 * tabSize };
|
|
||||||
}
|
|
||||||
export const BUTTON_PAIR_STYLE = `
|
|
||||||
.sim-button {
|
|
||||||
pointer-events: none;
|
|
||||||
fill: #000;
|
|
||||||
}
|
|
||||||
.sim-button-outer:active ~ .sim-button,
|
|
||||||
.sim-button-virtual:active {
|
|
||||||
fill: #FFA500;
|
|
||||||
}
|
|
||||||
.sim-button-outer {
|
|
||||||
cursor: pointer;
|
|
||||||
fill: #979797;
|
|
||||||
}
|
|
||||||
.sim-button-outer:hover {
|
|
||||||
stroke:gray;
|
|
||||||
stroke-width: ${PIN_DIST / 5}px;
|
|
||||||
}
|
|
||||||
.sim-button-nut {
|
|
||||||
fill:#000;
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
.sim-button-nut:hover {
|
|
||||||
stroke:${PIN_DIST / 15}px solid #704A4A;
|
|
||||||
}
|
|
||||||
.sim-button-tab {
|
|
||||||
fill:#FFF;
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
.sim-button-virtual {
|
|
||||||
cursor: pointer;
|
|
||||||
fill: rgba(255, 255, 255, 0.6);
|
|
||||||
stroke: rgba(255, 255, 255, 1);
|
|
||||||
stroke-width: ${PIN_DIST / 5}px;
|
|
||||||
}
|
|
||||||
.sim-button-virtual:hover {
|
|
||||||
stroke: rgba(128, 128, 128, 1);
|
|
||||||
}
|
|
||||||
.sim-text-virtual {
|
|
||||||
fill: #000;
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export class ButtonPairView implements IBoardPart<ButtonPairState> {
|
|
||||||
public element: SVGElement;
|
|
||||||
public defs: SVGElement[];
|
|
||||||
public style = BUTTON_PAIR_STYLE;
|
|
||||||
private state: ButtonPairState;
|
|
||||||
private bus: EventBus;
|
|
||||||
private aBtn: SVGGElement;
|
|
||||||
private bBtn: SVGGElement;
|
|
||||||
private abBtn: SVGGElement;
|
|
||||||
|
|
||||||
public init(bus: EventBus, state: ButtonPairState) {
|
|
||||||
this.state = state;
|
|
||||||
this.bus = bus;
|
|
||||||
this.defs = [];
|
|
||||||
this.element = this.mkBtns();
|
|
||||||
this.updateState();
|
|
||||||
this.attachEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
public moveToCoord(xy: Coord) {
|
|
||||||
let btnWidth = PIN_DIST * 3;
|
|
||||||
let [x, y] = xy;
|
|
||||||
translateEl(this.aBtn, [x, y])
|
|
||||||
translateEl(this.bBtn, [x + btnWidth, y])
|
|
||||||
translateEl(this.abBtn, [x + PIN_DIST * 1.5, y + PIN_DIST * 4])
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateState() {
|
|
||||||
let stateBtns = [this.state.aBtn, this.state.bBtn, this.state.abBtn];
|
|
||||||
let svgBtns = [this.aBtn, this.bBtn, this.abBtn];
|
|
||||||
|
|
||||||
if (this.state.usesButtonAB && this.abBtn.style.visibility != "visible") {
|
|
||||||
this.abBtn.style.visibility = "visible";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateTheme() {}
|
|
||||||
|
|
||||||
private mkBtns() {
|
|
||||||
this.aBtn = mkBtnSvg([0, 0]).el;
|
|
||||||
this.bBtn = mkBtnSvg([0, 0]).el;
|
|
||||||
|
|
||||||
const mkVirtualBtn = () => {
|
|
||||||
const numPins = 2;
|
|
||||||
const w = PIN_DIST * 2.8;
|
|
||||||
const offset = (w - (numPins * PIN_DIST)) / 2;
|
|
||||||
const corner = PIN_DIST / 2;
|
|
||||||
const cx = 0 - offset + w / 2;
|
|
||||||
const cy = cx;
|
|
||||||
const txtSize = PIN_DIST * 1.3;
|
|
||||||
const x = -offset;
|
|
||||||
const y = -offset;
|
|
||||||
const txtXOff = PIN_DIST / 7;
|
|
||||||
const txtYOff = PIN_DIST / 10;
|
|
||||||
|
|
||||||
let btng = <SVGGElement>svg.elt("g");
|
|
||||||
let btn = svg.child(btng, "rect", { class: "sim-button-virtual", x: x, y: y, rx: corner, ry: corner, width: w, height: w});
|
|
||||||
let btnTxt = mkTxt(cx + txtXOff, cy + txtYOff, txtSize, 0, "A+B");
|
|
||||||
svg.addClass(btnTxt, "sim-text")
|
|
||||||
svg.addClass(btnTxt, "sim-text-virtual");
|
|
||||||
btng.appendChild(btnTxt);
|
|
||||||
|
|
||||||
return btng;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.abBtn = mkVirtualBtn();
|
|
||||||
this.abBtn.style.visibility = "hidden";
|
|
||||||
|
|
||||||
let el = svg.elt("g");
|
|
||||||
svg.addClass(el, "sim-buttonpair")
|
|
||||||
el.appendChild(this.aBtn);
|
|
||||||
el.appendChild(this.bBtn);
|
|
||||||
el.appendChild(this.abBtn);
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
private attachEvents() {
|
|
||||||
let btnStates = [this.state.aBtn, this.state.bBtn];
|
|
||||||
let btnSvgs = [this.aBtn, this.bBtn];
|
|
||||||
btnSvgs.forEach((btn, index) => {
|
|
||||||
btn.addEventListener(pointerEvents.down, ev => {
|
|
||||||
btnStates[index].pressed = true;
|
|
||||||
})
|
|
||||||
btn.addEventListener(pointerEvents.leave, ev => {
|
|
||||||
btnStates[index].pressed = false;
|
|
||||||
})
|
|
||||||
btn.addEventListener(pointerEvents.up, ev => {
|
|
||||||
btnStates[index].pressed = false;
|
|
||||||
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
|
||||||
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
let updateBtns = (s: boolean) => {
|
|
||||||
btnStates.forEach(b => b.pressed = s)
|
|
||||||
};
|
|
||||||
this.abBtn.addEventListener(pointerEvents.down, ev => {
|
|
||||||
updateBtns(true);
|
|
||||||
})
|
|
||||||
this.abBtn.addEventListener(pointerEvents.leave, ev => {
|
|
||||||
updateBtns(false);
|
|
||||||
})
|
|
||||||
this.abBtn.addEventListener(pointerEvents.up, ev => {
|
|
||||||
updateBtns(false);
|
|
||||||
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_UP);
|
|
||||||
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user