Merge pull request #225 from Microsoft/generic-part
Adds support for generic parts; fixes generic boards to utilize BoardHost
This commit is contained in:
commit
c6c133ef9e
4
docs/static/hardware/.gitignore
vendored
4
docs/static/hardware/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
# don't check in until OSS request is approved
|
||||
neopixel-black-60-vert.svg
|
||||
sparkfun-*
|
||||
raspberrypi-*
|
||||
raspberrypi-*
|
||||
arduino-*
|
@ -71,11 +71,7 @@ namespace pxsim {
|
||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||
let options = (msg.options || {}) as RuntimeOptions;
|
||||
|
||||
//TODO: read from pxt.json/pxttarget.json
|
||||
let boardDef = MICROBIT_DEF;
|
||||
// let boardDef = ARDUINO_ZERO;
|
||||
// let boardDef = SPARKFUN_PHOTON;
|
||||
// let boardDef = RASPBERRYPI_MODELB;
|
||||
let boardDef = CURRENT_BOARD; //TODO: read from pxt.json/pxttarget.json
|
||||
|
||||
let cmpsList = msg.parts;
|
||||
let cmpDefs = PART_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json
|
||||
|
@ -7,10 +7,11 @@ namespace pxsim {
|
||||
export interface PinBlockDefinition {
|
||||
x: number,
|
||||
y: number,
|
||||
labelPosition: "above" | "below";
|
||||
labels: string[]
|
||||
}
|
||||
export interface BoardImageDefinition {
|
||||
image?: string,
|
||||
image: string,
|
||||
outlineImage?: string,
|
||||
width: number,
|
||||
height: number,
|
||||
@ -25,6 +26,8 @@ namespace pxsim {
|
||||
threeVoltPins: string[],
|
||||
attachPowerOnRight?: boolean,
|
||||
onboardComponents?: string[]
|
||||
useCrocClips?: boolean,
|
||||
marginWhenBreadboarding?: [number, number, number, number],
|
||||
}
|
||||
export interface FactoryFunctionPinAlloc {
|
||||
type: "factoryfunction",
|
||||
@ -44,9 +47,9 @@ namespace pxsim {
|
||||
image: string,
|
||||
width: number,
|
||||
height: number,
|
||||
left: number,
|
||||
top: number,
|
||||
pinDist: number,
|
||||
extraColumnOffset?: number,
|
||||
firstPin: [number, number],
|
||||
}
|
||||
export interface PartDefinition {
|
||||
visual: string | PartVisualDefinition,
|
||||
@ -99,8 +102,11 @@ namespace pxsim {
|
||||
groundPins: ["GND"],
|
||||
threeVoltPins: ["+3v3"],
|
||||
attachPowerOnRight: true,
|
||||
onboardComponents: ["buttonpair", "ledmatrix"],
|
||||
onboardComponents: ["buttonpair", "ledmatrix", "speaker"],
|
||||
useCrocClips: true,
|
||||
marginWhenBreadboarding: [0, 0, 80, 0],
|
||||
}
|
||||
|
||||
export const PART_DEFINITIONS: Map<PartDefinition> = {
|
||||
"ledmatrix": {
|
||||
visual: "ledmatrix",
|
||||
@ -162,9 +168,9 @@ namespace pxsim {
|
||||
image: "/static/hardware/speaker.svg",
|
||||
width: 500,
|
||||
height: 500,
|
||||
left: -180,
|
||||
top: -135,
|
||||
firstPin: [180, 135],
|
||||
pinDist: 70,
|
||||
extraColumnOffset: 1,
|
||||
},
|
||||
breadboardColumnsNeeded: 5,
|
||||
breadboardStartRow: "f",
|
||||
@ -174,8 +180,8 @@ namespace pxsim {
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "white", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 3], end: "ground", color: "white", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "#ff80fa", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 3], end: "ground", color: "blue", assemblyStep: 1},
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -202,4 +208,7 @@ namespace pxsim {
|
||||
"ledmatrix": (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8),
|
||||
"neopixel": (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy),
|
||||
};
|
||||
|
||||
//TODO: add multiple board support
|
||||
export const CURRENT_BOARD = MICROBIT_DEF;
|
||||
}
|
@ -128,10 +128,18 @@ namespace pxsim.instructions {
|
||||
cmpHeight?: number,
|
||||
cmpScale?: number
|
||||
};
|
||||
function mkBoardImgSvg(def: BoardImageDefinition): visuals.SVGElAndSize {
|
||||
return new visuals.MicrobitBoardSvg({
|
||||
theme: visuals.randomTheme()
|
||||
}).getView();
|
||||
function mkBoardImgSvg(def: string | BoardImageDefinition): visuals.SVGElAndSize {
|
||||
let boardView: visuals.BoardView;
|
||||
if (def === "microbit") {
|
||||
boardView = new visuals.MicrobitBoardSvg({
|
||||
theme: visuals.randomTheme()
|
||||
})
|
||||
} else {
|
||||
boardView = new visuals.GenericBoardSvg({
|
||||
visualDef: <BoardImageDefinition>def
|
||||
})
|
||||
}
|
||||
return boardView.getView();
|
||||
}
|
||||
function mkBBSvg(): visuals.SVGElAndSize {
|
||||
let bb = new visuals.Breadboard({});
|
||||
@ -270,14 +278,18 @@ namespace pxsim.instructions {
|
||||
div.appendChild(svgEl);
|
||||
return div;
|
||||
}
|
||||
function mkCmpDiv(type: "wire" | string, opts: mkCmpDivOpts): HTMLElement {
|
||||
function mkCmpDiv(cmp: "wire" | string | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
|
||||
let el: visuals.SVGElAndSize;
|
||||
if (type == "wire") {
|
||||
if (cmp == "wire") {
|
||||
//TODO: support non-croc wire parts
|
||||
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", true);
|
||||
} else {
|
||||
let cnstr = builtinComponentPartVisual[type];
|
||||
} else if (typeof cmp == "string") {
|
||||
let builtinVis = <string>cmp;
|
||||
let cnstr = builtinComponentPartVisual[builtinVis];
|
||||
el = cnstr([0, 0]);
|
||||
} else {
|
||||
let partVis = <PartVisualDefinition> cmp;
|
||||
el = visuals.mkGenericPartSVG(partVis);
|
||||
}
|
||||
return wrapSvg(el, opts);
|
||||
}
|
||||
@ -406,7 +418,8 @@ namespace pxsim.instructions {
|
||||
if (cmps) {
|
||||
cmps.forEach(cmpInst => {
|
||||
let cmp = board.addComponent(cmpInst)
|
||||
let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn}`];
|
||||
let colOffset = (<any>cmpInst.visual).breadboardStartColIdx || 0;
|
||||
let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${colOffset + cmpInst.breadboardStartColumn}`];
|
||||
//last step
|
||||
if (i === step) {
|
||||
board.highlightBreadboardPin(rowCol);
|
||||
@ -432,7 +445,7 @@ namespace pxsim.instructions {
|
||||
let panel = mkPanel();
|
||||
|
||||
// board and breadboard
|
||||
let boardImg = mkBoardImgSvg(<BoardImageDefinition>props.boardDef.visual);
|
||||
let boardImg = mkBoardImgSvg(props.boardDef.visual);
|
||||
let board = wrapSvg(boardImg, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE});
|
||||
panel.appendChild(board);
|
||||
let bbRaw = mkBBSvg();
|
||||
@ -447,18 +460,13 @@ namespace pxsim.instructions {
|
||||
if (c.visual === "buttonpair") {
|
||||
quant = 2;
|
||||
}
|
||||
if (typeof c.visual === "string") {
|
||||
let builtinVisual = <string>c.visual;
|
||||
let cmp = mkCmpDiv(builtinVisual, {
|
||||
left: QUANT_LBL(quant),
|
||||
leftSize: QUANT_LBL_SIZE,
|
||||
cmpScale: PARTS_CMP_SCALE,
|
||||
});
|
||||
addClass(cmp, "partslist-cmp");
|
||||
panel.appendChild(cmp);
|
||||
} else {
|
||||
//TODO: handle generic components
|
||||
}
|
||||
let cmp = mkCmpDiv(c.visual, {
|
||||
left: QUANT_LBL(quant),
|
||||
leftSize: QUANT_LBL_SIZE,
|
||||
cmpScale: PARTS_CMP_SCALE,
|
||||
});
|
||||
addClass(cmp, "partslist-cmp");
|
||||
panel.appendChild(cmp);
|
||||
});
|
||||
|
||||
// wires
|
||||
@ -529,19 +537,14 @@ namespace pxsim.instructions {
|
||||
}
|
||||
locs.forEach((l, i) => {
|
||||
let [row, col] = l;
|
||||
if (typeof c.visual === "string") {
|
||||
let builtinVisual = <string>c.visual;
|
||||
let cmp = mkCmpDiv(builtinVisual, {
|
||||
top: `(${row},${col})`,
|
||||
topSize: LOC_LBL_SIZE,
|
||||
cmpHeight: REQ_CMP_HEIGHT,
|
||||
cmpScale: REQ_CMP_SCALE
|
||||
})
|
||||
addClass(cmp, "cmp-div");
|
||||
reqsDiv.appendChild(cmp);
|
||||
} else {
|
||||
//TODO: generic component
|
||||
}
|
||||
let cmp = mkCmpDiv(c.visual, {
|
||||
top: `(${row},${col})`,
|
||||
topSize: LOC_LBL_SIZE,
|
||||
cmpHeight: REQ_CMP_HEIGHT,
|
||||
cmpScale: REQ_CMP_SCALE
|
||||
})
|
||||
addClass(cmp, "cmp-div");
|
||||
reqsDiv.appendChild(cmp);
|
||||
});
|
||||
});
|
||||
|
||||
@ -633,7 +636,7 @@ ${tsPackage}
|
||||
|
||||
style.textContent += STYLE;
|
||||
|
||||
const boardDef = MICROBIT_DEF;
|
||||
const boardDef = CURRENT_BOARD;
|
||||
const cmpDefs = PART_DEFINITIONS;
|
||||
|
||||
//props
|
||||
|
@ -142,6 +142,28 @@ namespace pxsim.visuals {
|
||||
};
|
||||
}
|
||||
|
||||
export function mkScaleFn(originUnit: number, targetUnit: number): (n: number) => number {
|
||||
return (n: number) => n * (targetUnit / originUnit);
|
||||
}
|
||||
export interface MkImageOpts {
|
||||
image: string,
|
||||
width: number,
|
||||
height: number,
|
||||
imageUnitDist: number,
|
||||
targetUnitDist: number
|
||||
}
|
||||
export function mkImageSVG(opts: MkImageOpts): SVGAndSize<SVGImageElement> {
|
||||
let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist);
|
||||
let w = scaleFn(opts.width);
|
||||
let h = scaleFn(opts.height);
|
||||
let img = <SVGImageElement>svg.elt("image", {
|
||||
width: w,
|
||||
height: h,
|
||||
"href": `${opts.image}`
|
||||
});
|
||||
return {el: img, w: w, h: h, x: 0, y: 0};
|
||||
}
|
||||
|
||||
export type Coord = [number, number];
|
||||
export function findDistSqrd(a: Coord, b: Coord): number {
|
||||
let x = a[0] - b[0];
|
||||
|
@ -21,32 +21,43 @@ namespace pxsim.visuals {
|
||||
private style: SVGStyleElement;
|
||||
private defs: SVGDefsElement;
|
||||
private state: DalBoard;
|
||||
private useCrocClips: boolean;
|
||||
|
||||
constructor(opts: BoardHostOpts) {
|
||||
this.state = opts.state;
|
||||
let onboardCmps = opts.boardDef.onboardComponents || [];
|
||||
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
|
||||
activeComponents.sort();
|
||||
this.useCrocClips = opts.boardDef.useCrocClips;
|
||||
|
||||
this.boardView = new visuals.MicrobitBoardSvg({
|
||||
runtime: runtime,
|
||||
theme: visuals.randomTheme(),
|
||||
disableTilt: false,
|
||||
wireframe: opts.wireframe,
|
||||
});
|
||||
if (opts.boardDef.visual === "microbit") {
|
||||
this.boardView = new visuals.MicrobitBoardSvg({
|
||||
runtime: runtime,
|
||||
theme: visuals.randomTheme(),
|
||||
disableTilt: false,
|
||||
wireframe: opts.wireframe,
|
||||
});
|
||||
} else {
|
||||
let boardVis = opts.boardDef.visual as BoardImageDefinition;
|
||||
this.boardView = new visuals.GenericBoardSvg({
|
||||
visualDef: boardVis,
|
||||
wireframe: opts.wireframe,
|
||||
});
|
||||
}
|
||||
|
||||
let useBreadboard = 0 < activeComponents.length || opts.forceBreadboard;
|
||||
if (useBreadboard) {
|
||||
this.breadboard = new Breadboard({
|
||||
wireframe: opts.wireframe,
|
||||
});
|
||||
let bMarg = opts.boardDef.marginWhenBreadboarding || [0, 0, 40, 0];
|
||||
let composition = composeSVG({
|
||||
el1: this.boardView.getView(),
|
||||
scaleUnit1: this.boardView.getPinDist(),
|
||||
el2: this.breadboard.getSVGAndSize(),
|
||||
scaleUnit2: this.breadboard.getPinDist(),
|
||||
margin: [0, 0, 20, 0],
|
||||
middleMargin: 80,
|
||||
margin: [bMarg[0], bMarg[1], 20, bMarg[3]],
|
||||
middleMargin: bMarg[2],
|
||||
maxWidth: opts.maxWidth,
|
||||
maxHeight: opts.maxHeight,
|
||||
});
|
||||
@ -138,35 +149,36 @@ namespace pxsim.visuals {
|
||||
|
||||
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
|
||||
let cmp: IBoardComponent<any> = null;
|
||||
let colOffset = 0;
|
||||
if (typeof cmpDesc.visual === "string") {
|
||||
let builtinVisual = cmpDesc.visual as string;
|
||||
let cnstr = builtinComponentSimVisual[builtinVisual];
|
||||
let stateFn = builtinComponentSimState[builtinVisual];
|
||||
cmp = cnstr();
|
||||
cmp.init(this.state.bus, stateFn(this.state), this.view, cmpDesc.microbitPins, cmpDesc.otherArgs);
|
||||
this.components.push(cmp);
|
||||
this.view.appendChild(cmp.element);
|
||||
if (cmp.defs)
|
||||
cmp.defs.forEach(d => this.defs.appendChild(d));
|
||||
this.style.textContent += cmp.style || "";
|
||||
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`];
|
||||
let coord = this.getBBCoord(rowCol);
|
||||
cmp.moveToCoord(coord);
|
||||
let getCmpClass = (type: string) => `sim-${type}-cmp`;
|
||||
let cls = getCmpClass(name);
|
||||
svg.addClass(cmp.element, cls);
|
||||
svg.addClass(cmp.element, "sim-cmp");
|
||||
cmp.updateTheme();
|
||||
cmp.updateState();
|
||||
} else {
|
||||
let vis = cmpDesc.visual as PartVisualDefinition;
|
||||
console.log("TODO PART: " + vis.image);
|
||||
//TODO: support generic parts
|
||||
cmp = new GenericPart(vis);
|
||||
colOffset = vis.extraColumnOffset || 0;
|
||||
}
|
||||
this.components.push(cmp);
|
||||
this.view.appendChild(cmp.element);
|
||||
if (cmp.defs)
|
||||
cmp.defs.forEach(d => this.defs.appendChild(d));
|
||||
this.style.textContent += cmp.style || "";
|
||||
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${colOffset + cmpDesc.breadboardStartColumn}`];
|
||||
let coord = this.getBBCoord(rowCol);
|
||||
cmp.moveToCoord(coord);
|
||||
let getCmpClass = (type: string) => `sim-${type}-cmp`;
|
||||
let cls = getCmpClass(name);
|
||||
svg.addClass(cmp.element, cls);
|
||||
svg.addClass(cmp.element, "sim-cmp");
|
||||
cmp.updateTheme();
|
||||
cmp.updateState();
|
||||
return cmp;
|
||||
}
|
||||
public addWire(inst: WireInst): Wire {
|
||||
return this.wireFactory.addWire(inst.start, inst.end, inst.color, true);
|
||||
return this.wireFactory.addWire(inst.start, inst.end, inst.color, this.useCrocClips);
|
||||
}
|
||||
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
|
||||
let {powerWires, components} = basicWiresAndCmpsAndWires;
|
||||
|
@ -3,46 +3,6 @@
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim.visuals {
|
||||
const svg = pxsim.svg;
|
||||
|
||||
export interface IBoardSvgProps {
|
||||
runtime: pxsim.Runtime;
|
||||
boardDef: BoardDefinition;
|
||||
disableTilt?: boolean;
|
||||
activeComponents: string[];
|
||||
fnArgs?: any;
|
||||
componentDefinitions: Map<PartDefinition>;
|
||||
}
|
||||
|
||||
export const VIEW_WIDTH = 498;
|
||||
export const VIEW_HEIGHT = 725;
|
||||
const TOP_MARGIN = 20;
|
||||
const MID_MARGIN = 40;
|
||||
const BOT_MARGIN = 20;
|
||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
||||
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
|
||||
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
|
||||
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
|
||||
|
||||
export type ComputedBoardDimensions = {
|
||||
scaleFn: (n: number) => number,
|
||||
height: number,
|
||||
width: number,
|
||||
xOff: number,
|
||||
yOff: number
|
||||
};
|
||||
export function getBoardDimensions(vis: BoardImageDefinition): ComputedBoardDimensions {
|
||||
let scaleFn = (n: number) => n * (PIN_DIST / vis.pinDist);
|
||||
let width = scaleFn(vis.width);
|
||||
return {
|
||||
scaleFn: scaleFn,
|
||||
height: scaleFn(vis.height),
|
||||
width: width,
|
||||
xOff: (VIEW_WIDTH - width) / 2.0,
|
||||
yOff: TOP_MARGIN
|
||||
}
|
||||
}
|
||||
|
||||
export const BOARD_SYTLE = `
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
@ -53,52 +13,6 @@ namespace pxsim.visuals {
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
not supported by any browser */
|
||||
}
|
||||
svg.sim.grayscale {
|
||||
-moz-filter: grayscale(1);
|
||||
-webkit-filter: grayscale(1);
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.sim-text {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
font-size:25px;
|
||||
fill:#fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 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; }
|
||||
}
|
||||
|
||||
.sim-board-pin {
|
||||
fill:#999;
|
||||
@ -178,202 +92,72 @@ namespace pxsim.visuals {
|
||||
stroke:#000;
|
||||
}
|
||||
`;
|
||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
||||
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
|
||||
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
|
||||
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
|
||||
|
||||
export interface GenericBoardProps {
|
||||
visualDef: BoardImageDefinition;
|
||||
wireframe?: boolean;
|
||||
}
|
||||
|
||||
let nextBoardId = 0;
|
||||
export class GenericBoardSvg /*TODO: implements BoardView*/ {
|
||||
public hostElement: SVGSVGElement;
|
||||
export class GenericBoardSvg implements BoardView {
|
||||
private element: SVGSVGElement;
|
||||
private style: SVGStyleElement;
|
||||
private defs: SVGDefsElement;
|
||||
private g: SVGGElement;
|
||||
public board: pxsim.DalBoard;
|
||||
public background: SVGElement;
|
||||
private components: IBoardComponent<any>[];
|
||||
public breadboard: Breadboard;
|
||||
private underboard: SVGGElement;
|
||||
public boardDef: BoardDefinition;
|
||||
private boardDim: ComputedBoardDimensions;
|
||||
public componentDefs: Map<PartDefinition>;
|
||||
private boardEdges: number[];
|
||||
private background: SVGElement;
|
||||
private width: number;
|
||||
private height: number;
|
||||
private id: number;
|
||||
public bbX: number;
|
||||
public bbY: number;
|
||||
private boardTopEdge: number;
|
||||
private boardBotEdge: number;
|
||||
private wireFactory: WireFactory;
|
||||
//truth
|
||||
|
||||
// pins & labels
|
||||
//(truth)
|
||||
private allPins: GridPin[] = [];
|
||||
private allLabels: GridLabel[] = [];
|
||||
//cache
|
||||
//(cache)
|
||||
private pinNmToLbl: Map<GridLabel> = {};
|
||||
private pinNmToPin: Map<GridPin> = {};
|
||||
|
||||
constructor(public props: IBoardSvgProps) {
|
||||
constructor(public props: GenericBoardProps) {
|
||||
//TODO: handle wireframe mode
|
||||
this.id = nextBoardId++;
|
||||
this.boardDef = props.boardDef;
|
||||
this.boardDim = getBoardDimensions(<BoardImageDefinition>this.boardDef.visual);
|
||||
this.board = this.props.runtime.board as pxsim.DalBoard;
|
||||
this.board.updateView = () => this.updateState();
|
||||
this.hostElement = <SVGSVGElement>svg.elt("svg")
|
||||
svg.hydrate(this.hostElement, {
|
||||
let visDef = props.visualDef;
|
||||
let imgHref = props.wireframe ? visDef.outlineImage : visDef.image;
|
||||
let boardImgAndSize = mkImageSVG({
|
||||
image: imgHref,
|
||||
width: visDef.width,
|
||||
height: visDef.height,
|
||||
imageUnitDist: visDef.pinDist,
|
||||
targetUnitDist: PIN_DIST
|
||||
});
|
||||
let scaleFn = mkScaleFn(visDef.pinDist, PIN_DIST);
|
||||
this.width = boardImgAndSize.w;
|
||||
this.height = boardImgAndSize.h;
|
||||
let img = boardImgAndSize.el;
|
||||
this.element = <SVGSVGElement>svg.elt("svg");
|
||||
svg.hydrate(this.element, {
|
||||
"version": "1.0",
|
||||
"viewBox": `0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}`,
|
||||
"enable-background": `new 0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}`,
|
||||
"viewBox": `0 0 ${this.width} ${this.height}`,
|
||||
"class": `sim sim-board-id-${this.id}`,
|
||||
"x": "0px",
|
||||
"y": "0px"
|
||||
});
|
||||
this.style = <SVGStyleElement>svg.child(this.hostElement, "style", {});
|
||||
if (props.wireframe)
|
||||
svg.addClass(this.element, "sim-board-outline")
|
||||
this.style = <SVGStyleElement>svg.child(this.element, "style", {});
|
||||
this.style.textContent += BOARD_SYTLE;
|
||||
this.defs = <SVGDefsElement>svg.child(this.hostElement, "defs", {});
|
||||
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
|
||||
this.g = <SVGGElement>svg.elt("g");
|
||||
this.hostElement.appendChild(this.g);
|
||||
this.underboard = <SVGGElement>svg.child(this.g, "g", {class: "sim-underboard"});
|
||||
this.components = [];
|
||||
this.componentDefs = props.componentDefinitions;
|
||||
|
||||
// breadboard
|
||||
this.breadboard = new Breadboard({})
|
||||
this.g.appendChild(this.breadboard.bb);
|
||||
let bbSize = this.breadboard.getSVGAndSize();
|
||||
let [bbWidth, bbHeight] = [bbSize.w, bbSize.h];
|
||||
const bbX = (VIEW_WIDTH - bbWidth) / 2;
|
||||
this.bbX = bbX;
|
||||
const bbY = TOP_MARGIN + this.boardDim.height + MID_MARGIN;
|
||||
this.bbY = bbY;
|
||||
this.breadboard.updateLocation(bbX, bbY);
|
||||
|
||||
// edges
|
||||
this.boardTopEdge = TOP_MARGIN;
|
||||
this.boardBotEdge = TOP_MARGIN + this.boardDim.height;
|
||||
this.boardEdges = [this.boardTopEdge, this.boardBotEdge, bbY, bbY + bbHeight]
|
||||
|
||||
this.wireFactory = new WireFactory(this.underboard, this.g, this.boardEdges, this.style, this.getLocCoord.bind(this));
|
||||
|
||||
this.buildDom();
|
||||
|
||||
this.updateTheme();
|
||||
this.updateState();
|
||||
|
||||
let cmps = props.activeComponents;
|
||||
if (cmps.length) {
|
||||
let allocRes = allocateDefinitions({
|
||||
boardDef: this.boardDef,
|
||||
cmpDefs: this.componentDefs,
|
||||
fnArgs: this.props.fnArgs,
|
||||
getBBCoord: this.getBBCoord.bind(this),
|
||||
cmpList: props.activeComponents,
|
||||
});
|
||||
this.addAll(allocRes);
|
||||
}
|
||||
}
|
||||
|
||||
private getBoardPinCoord(pinNm: string): Coord {
|
||||
let pin = this.pinNmToPin[pinNm];
|
||||
if (!pin)
|
||||
return null;
|
||||
return [pin.cx, pin.cy];
|
||||
}
|
||||
private getBBCoord(rowCol: BBRowCol): Coord {
|
||||
let bbCoord = this.breadboard.getCoord(rowCol);
|
||||
if (!bbCoord)
|
||||
return null;
|
||||
let [x, y] = bbCoord;
|
||||
return [x + this.bbX, y + this.bbY];
|
||||
}
|
||||
|
||||
public getLocCoord(loc: Loc): Coord {
|
||||
let coord: Coord;
|
||||
if (loc.type === "breadboard") {
|
||||
let rowCol = (<BBLoc>loc).rowCol;
|
||||
coord = this.getBBCoord(rowCol);
|
||||
} else {
|
||||
let pinNm = (<BoardLoc>loc).pin;
|
||||
coord = this.getBoardPinCoord(pinNm);
|
||||
}
|
||||
if (!coord) {
|
||||
console.error("Unknown location: " + name)
|
||||
return [0, 0];
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
|
||||
private mkGrayCover(x: number, y: number, w: number, h: number) {
|
||||
let rect = <SVGRectElement>svg.elt("rect");
|
||||
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
|
||||
return rect;
|
||||
}
|
||||
|
||||
private getCmpClass = (type: string) => `sim-${type}-cmp`;
|
||||
|
||||
public addWire(inst: WireInst): Wire {
|
||||
return this.wireFactory.addWire(inst.start, inst.end, inst.color);
|
||||
}
|
||||
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
|
||||
let {powerWires, components} = basicWiresAndCmpsAndWires;
|
||||
powerWires.forEach(w => this.addWire(w));
|
||||
components.forEach((cAndWs, idx) => {
|
||||
let {component, wires} = cAndWs;
|
||||
wires.forEach(w => this.addWire(w));
|
||||
this.addComponent(component);
|
||||
});
|
||||
}
|
||||
|
||||
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
|
||||
let cmp: IBoardComponent<any> = null;
|
||||
if (typeof cmpDesc.visual === "string") {
|
||||
let builtinVisual = cmpDesc.visual as string;
|
||||
let cnstr = builtinComponentSimVisual[builtinVisual];
|
||||
let stateFn = builtinComponentSimState[builtinVisual];
|
||||
let state = stateFn(this.board);
|
||||
cmp = cnstr();
|
||||
cmp.init(this.board.bus, state, this.hostElement, cmpDesc.microbitPins, cmpDesc.otherArgs);
|
||||
this.components.push(cmp);
|
||||
this.g.appendChild(cmp.element);
|
||||
if (cmp.defs)
|
||||
cmp.defs.forEach(d => this.defs.appendChild(d));
|
||||
this.style.textContent += cmp.style || "";
|
||||
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`];
|
||||
let coord = this.getBBCoord(rowCol);
|
||||
cmp.moveToCoord(coord);
|
||||
let cls = this.getCmpClass(name);
|
||||
svg.addClass(cmp.element, cls);
|
||||
svg.addClass(cmp.element, "sim-cmp");
|
||||
cmp.updateTheme();
|
||||
cmp.updateState();
|
||||
} else {
|
||||
//TODO: adding generic components
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
private updateTheme() {
|
||||
this.components.forEach(c => c.updateTheme());
|
||||
}
|
||||
|
||||
public updateState() {
|
||||
let state = this.board;
|
||||
if (!state) return;
|
||||
|
||||
this.components.forEach(c => c.updateState());
|
||||
|
||||
if (!runtime || runtime.dead) svg.addClass(this.hostElement, "grayscale");
|
||||
else svg.removeClass(this.hostElement, "grayscale");
|
||||
}
|
||||
|
||||
private buildDom() {
|
||||
|
||||
// filters
|
||||
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" })
|
||||
this.element.appendChild(this.g);
|
||||
|
||||
// main board
|
||||
this.background = svg.child(this.g, "image",
|
||||
{ class: "sim-board", x: this.boardDim.xOff, y: this.boardDim.yOff, width: this.boardDim.width, height: this.boardDim.height,
|
||||
"href": `${(<BoardImageDefinition>this.boardDef.visual).image}`});
|
||||
let backgroundCover = this.mkGrayCover(this.boardDim.xOff, this.boardDim.yOff, this.boardDim.width, this.boardDim.height);
|
||||
this.g.appendChild(img);
|
||||
this.background = img;
|
||||
svg.hydrate(img, { class: "sim-board" });
|
||||
let backgroundCover = this.mkGrayCover(0, 0, this.width, this.height);
|
||||
this.g.appendChild(backgroundCover);
|
||||
|
||||
// ----- pins
|
||||
@ -398,8 +182,8 @@ namespace pxsim.visuals {
|
||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
||||
}
|
||||
const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
|
||||
let xOffset = this.boardDim.xOff + this.boardDim.scaleFn(pinBlock.x) + PIN_DIST / 2.0;
|
||||
let yOffset = this.boardDim.yOff + this.boardDim.scaleFn(pinBlock.y) + PIN_DIST / 2.0;
|
||||
let xOffset = scaleFn(pinBlock.x) + PIN_DIST / 2.0;
|
||||
let yOffset = scaleFn(pinBlock.y) + PIN_DIST / 2.0;
|
||||
let rowCount = 1;
|
||||
let colCount = pinBlock.labels.length;
|
||||
let getColName = (colIdx: number) => pinBlock.labels[colIdx];
|
||||
@ -422,9 +206,11 @@ namespace pxsim.visuals {
|
||||
svg.addClass(gridRes.g, "sim-board-pin-group");
|
||||
return gridRes;
|
||||
};
|
||||
let pinBlocks = (<BoardImageDefinition>this.boardDef.visual).pinBlocks.map(mkPinBlockGrid);
|
||||
pinBlocks.forEach(blk => blk.allPins.forEach(p => {
|
||||
let pinBlocks = visDef.pinBlocks.map(mkPinBlockGrid);
|
||||
let pinToBlockDef: PinBlockDefinition[] = [];
|
||||
pinBlocks.forEach((blk, blkIdx) => blk.allPins.forEach((p, pIdx) => {
|
||||
this.allPins.push(p);
|
||||
pinToBlockDef.push(visDef.pinBlocks[blkIdx]);
|
||||
}));
|
||||
//tooltip
|
||||
this.allPins.forEach(p => {
|
||||
@ -443,15 +229,12 @@ namespace pxsim.visuals {
|
||||
});
|
||||
|
||||
// ----- labels
|
||||
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string): SVGTextElement => {
|
||||
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string, pos: "above" | "below"): SVGTextElement => {
|
||||
//TODO: extract constants
|
||||
let lblY: number;
|
||||
let lblX: number;
|
||||
let edges = [this.boardTopEdge, this.boardBotEdge];
|
||||
let distFromTopBot = edges.map(e => Math.abs(e - pinY));
|
||||
let closestEdgeIdx = distFromTopBot.reduce((pi, n, ni) => n < distFromTopBot[pi] ? ni : pi, 0);
|
||||
let topEdge = closestEdgeIdx == 0;
|
||||
if (topEdge) {
|
||||
|
||||
if (pos === "below") {
|
||||
let lblLen = size * 0.25 * txt.length;
|
||||
lblX = pinX;
|
||||
lblY = pinY + 12 + lblLen;
|
||||
@ -463,16 +246,17 @@ namespace pxsim.visuals {
|
||||
let el = mkTxt(lblX, lblY, size, -90, txt);
|
||||
return el;
|
||||
};
|
||||
const mkLabel = (pinX: number, pinY: number, txt: string): GridLabel => {
|
||||
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt);
|
||||
const mkLabel = (pinX: number, pinY: number, txt: string, pos: "above" | "below"): GridLabel => {
|
||||
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt, pos);
|
||||
svg.addClass(el, "sim-board-pin-lbl");
|
||||
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt);
|
||||
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt, pos);
|
||||
svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
|
||||
let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
|
||||
return label;
|
||||
}
|
||||
this.allLabels = this.allPins.map(p => {
|
||||
return mkLabel(p.cx, p.cy, p.col);
|
||||
this.allLabels = this.allPins.map((p, pIdx) => {
|
||||
let blk = pinToBlockDef[pIdx];
|
||||
return mkLabel(p.cx, p.cy, p.col, blk.labelPosition);
|
||||
});
|
||||
//attach labels
|
||||
this.allLabels.forEach(l => {
|
||||
@ -486,7 +270,29 @@ namespace pxsim.visuals {
|
||||
});
|
||||
}
|
||||
|
||||
public highlightLoc(pinNm: string) {
|
||||
public getCoord(pinNm: string): Coord {
|
||||
let pin = this.pinNmToPin[pinNm];
|
||||
if (!pin)
|
||||
return null;
|
||||
return [pin.cx, pin.cy];
|
||||
}
|
||||
|
||||
private mkGrayCover(x: number, y: number, w: number, h: number) {
|
||||
let rect = <SVGRectElement>svg.elt("rect");
|
||||
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
public getView(): SVGAndSize<SVGSVGElement> {
|
||||
return {el: this.element, w: this.width, h: this.height, x: 0, y: 0};
|
||||
}
|
||||
|
||||
public getPinDist() {
|
||||
return PIN_DIST;
|
||||
}
|
||||
|
||||
public highlightPin(pinNm: string) {
|
||||
let lbl = this.pinNmToLbl[pinNm];
|
||||
let pin = this.pinNmToPin[pinNm];
|
||||
if (lbl && pin) {
|
||||
@ -496,20 +302,5 @@ namespace pxsim.visuals {
|
||||
svg.addClass(pin.hoverEl, "highlight");
|
||||
}
|
||||
}
|
||||
|
||||
public highlightWire(wire: Wire) {
|
||||
//underboard wires
|
||||
wire.wires.forEach(e => {
|
||||
(<any>e).style["visibility"] = "visible";
|
||||
});
|
||||
|
||||
//un greyed out
|
||||
[wire.end1, wire.end2].forEach(e => {
|
||||
svg.addClass(e, "highlight");
|
||||
});
|
||||
wire.wires.forEach(e => {
|
||||
svg.addClass(e, "highlight");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,40 @@
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class GenericComponentView implements IBoardComponent<any> {
|
||||
public style: string;
|
||||
export function mkGenericPartSVG(partVisual: PartVisualDefinition): SVGAndSize<SVGImageElement> {
|
||||
let imgAndSize = mkImageSVG({
|
||||
image: partVisual.image,
|
||||
width: partVisual.width,
|
||||
height: partVisual.height,
|
||||
imageUnitDist: partVisual.pinDist,
|
||||
targetUnitDist: PIN_DIST
|
||||
});
|
||||
return imgAndSize;
|
||||
}
|
||||
|
||||
export class GenericPart implements IBoardComponent<any> {
|
||||
public style: string = "";
|
||||
public element: SVGElement;
|
||||
defs: SVGElement[];
|
||||
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void {
|
||||
defs: SVGElement[] = [];
|
||||
|
||||
constructor(partVisual: PartVisualDefinition) {
|
||||
let imgAndSize = mkGenericPartSVG(partVisual);
|
||||
let img = imgAndSize.el;
|
||||
let scaleFn = mkScaleFn(partVisual.pinDist, PIN_DIST);
|
||||
let [pinX, pinY] = partVisual.firstPin;
|
||||
let left = -scaleFn(pinX);
|
||||
let top = -scaleFn(pinY);
|
||||
translateEl(img, [left, top]); // So that 0,0 is on the first pin
|
||||
this.element = svg.elt("g");
|
||||
this.element.appendChild(img);
|
||||
}
|
||||
|
||||
moveToCoord(xy: Coord): void {
|
||||
translateEl(this.element, xy);
|
||||
}
|
||||
updateState(): void {
|
||||
}
|
||||
updateTheme(): void {
|
||||
}
|
||||
|
||||
//unused
|
||||
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void { }
|
||||
updateState(): void { }
|
||||
updateTheme(): void { }
|
||||
}
|
||||
}
|
@ -92,8 +92,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
public moveToCoord(xy: Coord) {
|
||||
let [x, y] = xy;
|
||||
translateEl(this.element, [x, y]);
|
||||
translateEl(this.element, xy);
|
||||
}
|
||||
|
||||
public updateTheme() {
|
||||
|
Loading…
Reference in New Issue
Block a user