refacotring various simulator features into pxt
This commit is contained in:
parent
33f12f9ecc
commit
910772d54e
@ -29,7 +29,13 @@ namespace pxsim {
|
||||
|
||||
// components
|
||||
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.radioState = new RadioState(runtime);
|
||||
this.accelerometerState = new AccelerometerState(runtime);
|
||||
|
@ -226,25 +226,6 @@ namespace pxsim.visuals {
|
||||
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 {
|
||||
public element: SVGSVGElement;
|
||||
private style: SVGStyleElement;
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace pxsim.input {
|
||||
export function onButtonPressed(button: number, handler: RefAction): void {
|
||||
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;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
@ -10,32 +10,12 @@ namespace pxsim.input {
|
||||
|
||||
export function buttonIsPressed(button: number): boolean {
|
||||
let b = board().buttonPairState;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
||||
if (button == b.abBtn.id && !b.usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_A) return b.aBtn.pressed;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_B) return b.bBtn.pressed;
|
||||
if (button == b.aBtn.id) return b.aBtn.pressed;
|
||||
if (button == b.bBtn.id) return 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,11 +12,4 @@ namespace pxsim.input {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
export class CompassState {
|
||||
usesHeading = false;
|
||||
heading = 90;
|
||||
}
|
||||
}
|
@ -116,72 +116,6 @@ namespace pxsim {
|
||||
}
|
||||
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 {
|
||||
|
@ -1,10 +1,3 @@
|
||||
namespace pxsim {
|
||||
export class LightSensorState {
|
||||
usesLightLevel = false;
|
||||
lightLevel = 128;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.input {
|
||||
export function lightLevel(): number {
|
||||
let b = board().lightSensorState;
|
||||
|
@ -22,58 +22,6 @@ namespace pxsim {
|
||||
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 {
|
||||
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