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