204 lines
7.4 KiB
TypeScript
204 lines
7.4 KiB
TypeScript
|
/// <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 IBoardComponent<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);
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|