namespace pxsim.visuals { export interface BoardHostOpts { state: DalBoard, boardDef: BoardDefinition, cmpsList?: string[], cmpDefs: Map, fnArgs: any, forceBreadboard?: boolean, maxWidth?: string, maxHeight?: string wireframe?: boolean } export class BoardHost { private components: IBoardComponent[] = []; private wireFactory: WireFactory; private breadboard: Breadboard; private fromBBCoord: (xy: Coord) => Coord; private fromMBCoord: (xy: Coord) => Coord; private boardView: BoardView; private view: SVGSVGElement; 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; 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: [bMarg[0], bMarg[1], 20, bMarg[3]], middleMargin: bMarg[2], maxWidth: opts.maxWidth, maxHeight: opts.maxHeight, }); let under = composition.under; let over = composition.over; this.view = composition.host; let edges = composition.edges; this.fromMBCoord = composition.toHostCoord1; this.fromBBCoord = composition.toHostCoord2; let pinDist = composition.scaleUnit; this.style = svg.child(this.view, "style", {}); this.defs = svg.child(this.view, "defs", {}); this.wireFactory = new WireFactory(under, over, edges, this.style, this.getLocCoord.bind(this)); let allocRes = allocateDefinitions({ boardDef: opts.boardDef, cmpDefs: opts.cmpDefs, fnArgs: opts.fnArgs, getBBCoord: this.breadboard.getCoord.bind(this.breadboard), cmpList: activeComponents, }); this.addAll(allocRes); } else { let el = this.boardView.getView().el; this.view = el; if (opts.maxWidth) svg.hydrate(this.view, { width: opts.maxWidth }); if (opts.maxHeight) svg.hydrate(this.view, { height: opts.maxHeight }); } this.state.updateSubscribers.push(() => this.updateState()); } public highlightBoardPin(pinNm: string) { this.boardView.highlightPin(pinNm); } public highlightBreadboardPin(rowCol: BBRowCol) { this.breadboard.highlightLoc(rowCol); } public highlightWire(wire: Wire) { //TODO: move to wiring.ts //underboard wires wire.wires.forEach(e => { svg.addClass(e, "highlight"); (e).style["visibility"] = "visible"; }); //un greyed out svg.addClass(wire.endG, "highlight"); } public getView(): SVGElement { return this.view; } private updateState() { this.components.forEach(c => c.updateState()); } private getBBCoord(rowCol: BBRowCol) { let bbCoord = this.breadboard.getCoord(rowCol); return this.fromBBCoord(bbCoord); } private getPinCoord(pin: string) { let boardCoord = this.boardView.getCoord(pin); return this.fromMBCoord(boardCoord); } public getLocCoord(loc: Loc): Coord { let coord: Coord; if (loc.type === "breadboard") { let rowCol = (loc).rowCol; coord = this.getBBCoord(rowCol); } else { let pinNm = (loc).pin; coord = this.getPinCoord(pinNm); } if (!coord) { console.error("Unknown location: " + name) return [0, 0]; } return coord; } public addComponent(cmpDesc: CmpInst): IBoardComponent { let cmp: IBoardComponent = 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); } else { let vis = cmpDesc.visual as PartVisualDefinition; 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 = [`${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, this.useCrocClips); } 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); }); } } }