/// /// /// namespace pxsim { export type BBRowCol = [/*row*/string, /*column*/string]; export type BoardPin = string; export interface BBLoc {type: "breadboard", rowCol: BBRowCol}; export interface BoardLoc {type: "dalboard", pin: BoardPin}; export type Loc = BBLoc | BoardLoc; export function initRuntimeWithDalBoard() { U.assert(!runtime.board); let b = new DalBoard(); runtime.board = b; } if (!pxsim.initCurrentRuntime) { pxsim.initCurrentRuntime = initRuntimeWithDalBoard; } export function board() { return runtime.board as DalBoard; } export function mkRange(a: number, b: number): number[] { let res: number[] = []; for (; a < b; a++) res.push(a); return res; } export function parseQueryString(): (key: string) => string { let qs = window.location.search.substring(1); let getQsVal = (key: string) => decodeURIComponent((qs.split(`${key}=`)[1] || "").split("&")[0] || "").replace(/\+/g, " "); return getQsVal; } } namespace pxsim.visuals { export interface IPointerEvents { up: string, down: string, move: string, leave: string } export const pointerEvents: IPointerEvents = !!(window as any).PointerEvent ? { up: "pointerup", down: "pointerdown", move: "pointermove", leave: "pointerleave" } : { up: "mouseup", down: "mousedown", move: "mousemove", leave: "mouseleave" }; export function translateEl(el: SVGElement, xy: [number, number]) { //TODO append translation instead of replacing the full transform svg.hydrate(el, {transform: `translate(${xy[0]} ${xy[1]})`}); } export interface ComposeOpts { el1: SVGAndSize, scaleUnit1: number, el2: SVGAndSize, scaleUnit2: number, margin: [number, number, number, number], middleMargin: number, maxWidth?: string, maxHeight?: string, } export interface ComposeResult { host: SVGSVGElement, scaleUnit: number, under: SVGGElement, over: SVGGElement, edges: number[], toHostCoord1: (xy: Coord) => Coord, toHostCoord2: (xy: Coord) => Coord, } export function composeSVG(opts: ComposeOpts): ComposeResult { let [a, b] = [opts.el1, opts.el2]; U.assert(a.x == 0 && a.y == 0 && b.x == 0 && b.y == 0, "el1 and el2 x,y offsets not supported"); let setXY = (e: SVGSVGElement, x: number, y: number) => svg.hydrate(e, {x: x, y: y}); let setWH = (e: SVGSVGElement, w: string, h: string) => { if (w) svg.hydrate(e, {width: w}); if (h) svg.hydrate(e, {height: h}); } let setWHpx = (e: SVGSVGElement, w: number, h: number) => svg.hydrate(e, {width: `${w}px`, height: `${h}px`}); let scaleUnit = opts.scaleUnit2; let aScalar = opts.scaleUnit2 / opts.scaleUnit1; let bScalar = 1.0; let aw = a.w * aScalar; let ah = a.h * aScalar; setWHpx(a.el, aw, ah); let bw = b.w * bScalar; let bh = b.h * bScalar; setWHpx(b.el, bw, bh); let [mt, mr, mb, ml] = opts.margin; let mm = opts.middleMargin; let innerW = Math.max(aw, bw); let ax = mr + (innerW - aw) / 2.0; let ay = mt; setXY(a.el, ax, ay); let bx = mr + (innerW - bw) / 2.0; let by = ay + ah + mm; setXY(b.el, bx, by); let edges = [ay, ay + ah, by, by + bh]; let w = mr + innerW + ml; let h = mt + ah + mm + bh + mb; let host = svg.elt("svg", { "version": "1.0", "viewBox": `0 0 ${w} ${h}`, "class": `sim-bb`, }); setWH(host, opts.maxWidth, opts.maxHeight); setXY(host, 0, 0); let under = svg.child(host, "g"); host.appendChild(a.el); host.appendChild(b.el); let over = svg.child(host, "g"); let toHostCoord1 = (xy: Coord): Coord => { let [x, y] = xy; return [x * aScalar + ax, y * aScalar + ay]; }; let toHostCoord2 = (xy: Coord): Coord => { let [x, y] = xy; return [x * bScalar + bx, y * bScalar + by]; }; return { under: under, over: over, host: host, edges: edges, scaleUnit: scaleUnit, toHostCoord1: toHostCoord1, toHostCoord2: toHostCoord2, }; } 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 { let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist); let w = scaleFn(opts.width); let h = scaleFn(opts.height); let img = 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]; let y = a[1] - b[1]; return x * x + y * y; } export function findClosestCoordIdx(a: Coord, bs: Coord[]): number { let dists = bs.map(b => findDistSqrd(a, b)); let minIdx = dists.reduce((prevIdx, currDist, currIdx, arr) => { return currDist < arr[prevIdx] ? currIdx : prevIdx; }, 0); return minIdx; } export interface IBoardComponent { style: string, element: SVGElement, defs: SVGElement[], init(bus: EventBus, state: T, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void, //NOTE: constructors not supported in interfaces moveToCoord(xy: Coord): void, updateState(): void, updateTheme(): void, } export function mkTxt(cx: number, cy: number, size: number, rot: number, txt: string, txtXOffFactor?: number, txtYOffFactor?: number): SVGTextElement { let el = svg.elt("text") //HACK: these constants (txtXOffFactor, txtYOffFactor) tweak the way this algorithm knows how to center the text txtXOffFactor = txtXOffFactor || -0.33333; txtYOffFactor = txtYOffFactor || 0.3; const xOff = txtXOffFactor * size * txt.length; const yOff = txtYOffFactor * size; svg.hydrate(el, {style: `font-size:${size}px;`, transform: `translate(${cx} ${cy}) rotate(${rot}) translate(${xOff} ${yOff})` }); svg.addClass(el, "noselect"); el.textContent = txt; return el; } export type WireColor = "black" | "white" | "gray" | "purple" | "blue" | "green" | "yellow" | "orange" | "red" | "brown"; export const WIRE_COLOR_MAP: Map = { black: "#514f4d", white: "#fcfdfc", gray: "#acabab", purple: "#a772a1", blue: "#01a6e8", green: "#3cce73", yellow: "#ece600", orange: "#fdb262", red: "#f44f43", brown: "#c89764", } export function mapWireColor(clr: WireColor | string): string { return WIRE_COLOR_MAP[clr] || clr; } export interface SVGAndSize { el: T, y: number, x: number, w: number, h: number }; export type SVGElAndSize = SVGAndSize; export const PIN_DIST = 15; export interface BoardView { getView(): SVGAndSize; getCoord(pinNm: string): Coord; getPinDist(): number; highlightPin(pinNm: string): void; } }