diff --git a/src/cpu/CanvasEventHandler.tsx b/src/cpu/CanvasEventHandler.tsx index 324009e..041cd52 100644 --- a/src/cpu/CanvasEventHandler.tsx +++ b/src/cpu/CanvasEventHandler.tsx @@ -494,7 +494,7 @@ export const CanvasEventHandler: React.FC<{ // since still want to be able to select the component itself. Also should // be related to zoom level let def = editorState.compLibrary.getCompDef(comp.defId); - let subMtx = mtx.mul(computeSubLayoutMatrix(comp, def!, def!.subLayout!)); + let subMtx = mtx.mul(computeSubLayoutMatrix(comp, def!, def!.subLayout!.layout)); let subSchematic = getCompSubSchematic(editorState, comp)!; let subRef = getRefUnderCursor(editorState, ev, subSchematic, subMtx, idPrefix + comp.id + '|'); diff --git a/src/cpu/CanvasRenderHelpers.tsx b/src/cpu/CanvasRenderHelpers.tsx index 424ce35..ead10b5 100644 --- a/src/cpu/CanvasRenderHelpers.tsx +++ b/src/cpu/CanvasRenderHelpers.tsx @@ -1,6 +1,6 @@ import { AffineMat2d } from "../utils/AffineMat2d"; import { getOrAddToMap, hasFlag } from "../utils/data"; -import { Vec3 } from "../utils/vector"; +import { BoundingBox3d, Vec3 } from "../utils/vector"; export enum FontType { @@ -26,13 +26,12 @@ export function makeCanvasFont(fontSize: number, fontType: FontType = FontType.D export interface ICanvasGridState { tileCanvases: Map; + region: BoundingBox3d; } -export function drawGrid(mtx: AffineMat2d, ctx: CanvasRenderingContext2D, gridState: ICanvasGridState, fillStyle = '#aaa') { - - let cvsSize = new Vec3(ctx.canvas.width, ctx.canvas.height); - let tl = mtx.mulVec3Inv(new Vec3()); - let br = mtx.mulVec3Inv(cvsSize); +export function drawGrid(mtx: AffineMat2d, ctx: CanvasRenderingContext2D, gridState: ICanvasGridState, fillStyle = '#aaa', special: boolean = false) { + let tl = mtx.mulVec3Inv(gridState.region.min); + let br = mtx.mulVec3Inv(gridState.region.max); // draw grid diff --git a/src/cpu/Clipboard.ts b/src/cpu/Clipboard.ts index 436fb03..3a03c8f 100644 --- a/src/cpu/Clipboard.ts +++ b/src/cpu/Clipboard.ts @@ -149,6 +149,7 @@ export function selectionToSchematic(editorState: IEditorState): ISchematic { } let snapshotPartial: ISchematic = { + compBbox: editorState.snapshot.compBbox, comps: editorState.snapshot.comps.filter(c => selectedCompIds.has(c.id)), wires: wires, }; diff --git a/src/cpu/CompDetails.tsx b/src/cpu/CompDetails.tsx new file mode 100644 index 0000000..26136df --- /dev/null +++ b/src/cpu/CompDetails.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { editComp, useEditorContext } from "./Editor"; +import { IEditSnapshot, RefType } from "./CpuModel"; +import { StringEditor } from "./displayTools/StringEditor"; +import { assignImm } from "../utils/data"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { getCompFromRef, getCompSubSchematic } from "./SubSchematics"; + +export const CompDetails: React.FC<{ +}> = ({ }) => { + + let editCtx = useEditorContext(); + let { editorState, setEditorState } = editCtx; + + let snapshot = editorState.snapshotTemp ?? editorState.snapshot; + let numSelected = snapshot.selected.length; + + let singleCompRef = numSelected === 1 && snapshot.selected[0].type === RefType.Comp ? snapshot.selected[0] : null; + let singleComp = singleCompRef ? getCompFromRef(editorState, singleCompRef.id) : null; + let compDef = singleComp ? editorState.compLibrary.getCompDef(singleComp.defId) : null; + + function handleNameUpdate(end: boolean, value: string) { + setEditorState(editComp({ idPrefix: "" }, end, singleComp!, comp => assignImm(comp, { name: value }))); + } + + function handleExtIdUpdate(end: boolean, value: string) { + setEditorState(editComp({ idPrefix: "" }, end, singleComp!, comp => assignImm(comp, { extId: value }))); + } + + function handleInternalSchematicAddNew() { + let newSchematic = editorState.schematicLibrary.addCustomSchematic('New Schematic')!; + newSchematic.model.parentCompDefId = singleComp!.defId; + // probably want to zoom into the new schematic?? + setEditorState(editComp({ idPrefix: "" }, true, singleComp!, comp => assignImm(comp, { subSchematicId: newSchematic.id }))); + } + + let subSchematic = singleComp ? getCompSubSchematic(editorState, singleComp) : null; + + return
+ {numSelected === 0 &&
No component selected
} + {numSelected === 1 && singleComp && compDef &&
+
{singleComp.name}
+
+ {singleComp.id} + {singleComp.defId} + + + {`${singleComp.pos}`} + {`${singleComp.size}`} +
+
+
Internal Schematic
+ {!subSchematic && } + {singleComp.subSchematicId && {singleComp.subSchematicId}} + {subSchematic && {(subSchematic as IEditSnapshot)?.name}} +
+
} +
; +}; + +export const EditKvp: React.FC<{ + label: string; + children?: React.ReactNode; +}> = ({ label, children }) => { + return
+
{label}
+
{children}
+
; +}; diff --git a/src/cpu/CompLayoutEditor.tsx b/src/cpu/CompLayoutEditor.tsx index 95bfd20..e693394 100644 --- a/src/cpu/CompLayoutEditor.tsx +++ b/src/cpu/CompLayoutEditor.tsx @@ -185,7 +185,7 @@ export const CompLayoutEditor: React.FC<{ ctx.scale(pr, pr); ctx.transform(...mtx.toTransformParams()); - drawGrid(mtx, ctx, { tileCanvases: bits.extraCanvases }, '#aaa'); + // drawGrid(mtx, ctx, { tileCanvases: bits.extraCanvases }, '#aaa'); ctx.restore(); diff --git a/src/cpu/CpuCanvas.tsx b/src/cpu/CpuCanvas.tsx index bc2e178..d750b3d 100644 --- a/src/cpu/CpuCanvas.tsx +++ b/src/cpu/CpuCanvas.tsx @@ -20,11 +20,13 @@ import { LibraryBrowser } from "./library/LibraryBrowser"; import { CompLayoutToolbar } from "./CompLayoutEditor"; import { palette } from "./palette"; import { drawGrid, makeCanvasFont } from "./CanvasRenderHelpers"; -import { computeSubLayoutMatrix } from "./SubSchematics"; +import { computeSubLayoutMatrix, getCompSubSchematic } from "./SubSchematics"; import { computeModelBoundingBox, computeZoomExtentMatrix, createCpuEditorState } from "./ModelHelpers"; import { MainToolbar } from "./toolbars/CpuToolbars"; import { SharedContextContext, createSharedContext } from "./library/SharedContext"; import { CompBoundingBox } from "./CompBoundingBox"; +import { CompDetails } from "./CompDetails"; +import { Resizer } from "../utils/Resizer"; interface ICanvasDragState { mtx: AffineMat2d; @@ -158,6 +160,7 @@ export const CpuCanvas: React.FC<{ ctx: el.getContext('2d')!, size: new Vec3(1, 1), scale: 1, + region: new BoundingBox3d(new Vec3(0, 0), new Vec3(1, 1)), tileCanvases: new Map(), mtx: AffineMat2d.identity(), } : null); @@ -187,6 +190,7 @@ export const CpuCanvas: React.FC<{ canvas.style.height = `${h}px`; cvsState.size.x = w; cvsState.size.y = h; + cvsState.region = new BoundingBox3d(new Vec3(0, 0), new Vec3(w, h)); cvsState.scale = 1.0 / editorState.mtx.a; cvsState.mtx = editorState.mtx; let pr = window.devicePixelRatio; @@ -228,14 +232,16 @@ export const CpuCanvas: React.FC<{ let compFullId = idPrefix + a.comp.id; let subLayoutDom = null; - if (a.subLayout) { - let subMtx = computeSubLayoutMatrix(a.comp, a.def, a.subLayout); + let subSchematic = getCompSubSchematic(editorState, a.comp); + if (a.subLayout || subSchematic) { + let schematic = a.subLayout?.layout ?? subSchematic!; + let subMtx = computeSubLayoutMatrix(a.comp, a.def, schematic); subLayoutDom =
- {getCompDomElements(a.subLayout.layout, idPrefix + a.comp.id + '|')} + {getCompDomElements(schematic, idPrefix + a.comp.id + '|')}
; } @@ -269,44 +275,66 @@ export const CpuCanvas: React.FC<{ return {!readonly && } -
- - {cvsState && -
-
- {compDivs} + +
+ + {cvsState && +
+
+ {compDivs} +
+ {editorState.transparentComps &&
}
- {editorState.transparentComps &&
} + } +
+ {!readonly && <> + + + + } + {!editorState.snapshotTemp && !editorState.maskHover && }
- } -
- {!readonly && <> - - - - } - {!editorState.snapshotTemp && !editorState.maskHover && } + {readonly &&
+ +
} +
+ {!readonly && } +
+ {editorState.compLibraryVisible && } + {children}
- {readonly &&
- -
} -
- {!readonly && } +
+
- {editorState.compLibraryVisible && } - {children} -
+ ; }; +function renderAxes(cvs: ICanvasState, editorState: IEditorState) { + let ctx = cvs.ctx; + ctx.save(); + ctx.lineWidth = 4 * cvs.scale; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(4, 0); + ctx.strokeStyle = "#f00"; + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, 4); + ctx.strokeStyle = "#0f0"; + ctx.stroke(); + ctx.restore(); +} + function renderCpu(cvs: ICanvasState, editorState: IEditorState, layout: ISchematic, exeSystem: IExeSystem, idPrefix = '') { let ctx = cvs.ctx; let snapshot = editorState.snapshotTemp ?? editorState.snapshot; - drawGrid(editorState.mtx, ctx, cvs); + drawGrid(editorState.mtx, ctx, cvs, '#aaa', !!idPrefix); for (let wire of layout.wires) { let exeNet = exeSystem.nets[exeSystem.lookup.wireIdToNetIdx.get(idPrefix + wire.id) ?? -1]; @@ -377,16 +405,25 @@ function renderCpu(cvs: ICanvasState, editorState: IEditorState, layout: ISchema renderCompPort(cvs, editorState, comp, node); } - if (compDef?.subLayout) { + let subSchematic = getCompSubSchematic(editorState, comp); + if (compDef && subSchematic) { // nested rendering!!!! ctx.save(); - let subMtx = computeSubLayoutMatrix(comp, compDef, compDef.subLayout); + let subMtx = computeSubLayoutMatrix(comp, compDef, subSchematic); ctx.transform(...subMtx.toTransformParams()); - let subCvs: ICanvasState = { ...cvs, mtx: subMtx, scale: cvs.scale / subMtx.a }; - renderCpu(subCvs, editorState, compDef.subLayout.layout, exeSystem, idPrefix + comp.id + '|'); + let innerMtx = cvs.mtx.mul(subMtx.inv()); + + let subCvs: ICanvasState = { + ...cvs, + mtx: cvs.mtx.mul(subMtx), + scale: cvs.scale / subMtx.a, + region: innerMtx.mulBb(new BoundingBox3d(comp.pos, comp.pos.add(comp.size))), + }; + + renderCpu(subCvs, editorState, subSchematic, exeSystem, idPrefix + comp.id + '|'); ctx.restore(); } @@ -427,6 +464,8 @@ function renderCpu(cvs: ICanvasState, editorState: IEditorState, layout: ISchema renderSelectRegion(cvs, editorState, idPrefix); renderComponentBoundingBox(cvs, editorState, snapshot, idPrefix); + + // renderAxes(cvs, editorState); } diff --git a/src/cpu/CpuModel.tsx b/src/cpu/CpuModel.tsx index 057e5b8..30a1947 100644 --- a/src/cpu/CpuModel.tsx +++ b/src/cpu/CpuModel.tsx @@ -168,6 +168,7 @@ export interface IHitTest { export interface ICanvasState { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D; + region: BoundingBox3d; size: Vec3; // derived scale: number; // derived mtx: AffineMat2d; // derived @@ -247,6 +248,7 @@ export interface IComp { defId: string; name: string; extId?: string; // an id that can be referenced "externally" + subSchematicId?: string; pos: Vec3; size: Vec3; ports: ICompPort[]; @@ -294,6 +296,7 @@ export interface IEditSnapshot { nextWireId: number; comps: IComp[]; wires: IWireGraph[]; + parentCompDefId?: string; // custom component def compSize: Vec3; @@ -303,11 +306,29 @@ export interface IEditSnapshot { subComps: Map; } +/** + * OK, how do we manage our components that are builtin, but we want to add schematics for? + * We want the multiple schematics to map to a builtin comp, and select the given schematic + * for a given comp. Mostly we want to edit the schematic from within a parent schematic, and then + * save it, ideally to that parent schematic (unless we want it to live on its own). + * + * Probably start with it living on its own. (since we can't save to the parent schematic yet) + * We'll have a field on the realized comp which says which schematic we're using underneath. + * + * So we click on a comp, and UI shows up to a) select from some pre-existing schematics, or b) create a new one. + * This will be on the RHS, and also have things like the extId of the component & other details. + * + * The schematic is tied to a particular comp, but sort of weakly, and it's clear that the comp ports + * are the source-of-truth. Probably have ability to disable/hide/ignore not-connected ports, which is defined by the + * presence of the port in the schematic. +*/ + interface IEditSchematic { nextCompId: number; nextWireId: number; comps: IComp[]; wires: IWireGraph[]; + compBbox: BoundingBox3d; } export interface IMemoryMap { diff --git a/src/cpu/Editor.tsx b/src/cpu/Editor.tsx index 6adf621..4102f7b 100644 --- a/src/cpu/Editor.tsx +++ b/src/cpu/Editor.tsx @@ -28,6 +28,7 @@ export function editComp(editCtx: IEditContext, end: boolean, comp: IComp, let comp2 = layout.comps.find(a => a.id === comp.id) as IComp | null; if (!comp2) { + console.log('unable to edit comp!!'); return layout; } diff --git a/src/cpu/HoverDisplay.tsx b/src/cpu/HoverDisplay.tsx index d957e97..1da0743 100644 --- a/src/cpu/HoverDisplay.tsx +++ b/src/cpu/HoverDisplay.tsx @@ -126,7 +126,7 @@ export const HoverDisplay: React.FC<{ if (!comp || !def) { break; } - let subMtx = computeSubLayoutMatrix(comp, def, def.subLayout!); + let subMtx = computeSubLayoutMatrix(comp, def, def.subLayout!.layout); mtx = mtx.mul(subMtx); } diff --git a/src/cpu/SubSchematics.tsx b/src/cpu/SubSchematics.tsx index 7e21010..713660c 100644 --- a/src/cpu/SubSchematics.tsx +++ b/src/cpu/SubSchematics.tsx @@ -1,16 +1,25 @@ +import { subscribe } from "diagnostics_channel"; import { AffineMat2d } from "../utils/AffineMat2d"; -import { BoundingBox3d } from "../utils/vector"; -import { IComp, IEditSnapshot, IEditorState, ISchematic } from "./CpuModel"; -import { ICompDef, ISubLayoutArgs } from "./comps/CompBuilder"; - -export function computeSubLayoutMatrix(comp: IComp, compDef: ICompDef, subLayout: ISubLayoutArgs) { - let subSchematic = subLayout.layout; - let bb = new BoundingBox3d(); - for (let c of subSchematic.comps) { - bb.addInPlace(c.pos); - bb.addInPlace(c.pos.add(c.size)); +import { BoundingBox3d, Vec3 } from "../utils/vector"; +import { IComp, IEditorState, ISchematic } from "./CpuModel"; +import { ICompDef } from "./comps/CompBuilder"; + +export function computeSubLayoutMatrix(comp: IComp, compDef: ICompDef, subSchematic: ISchematic) { + if (!subSchematic.comps) { + debugger; + } + + let bb = subSchematic.compBbox?.clone() ?? new BoundingBox3d(); + if (bb.empty) { + for (let c of subSchematic.comps) { + bb.addInPlace(c.pos); + bb.addInPlace(c.pos.add(c.size)); + } + bb.expandInPlace(Math.min(bb.size().x, bb.size().y) * 0.1); + } + if (bb.empty) { + bb = new BoundingBox3d(new Vec3(), comp.size.mul(2.5)); } - bb.expandInPlace(Math.min(bb.size().x, bb.size().y) * 0.1); let bbSize = bb.size(); let scale = Math.min(comp.size.x / bbSize.x, comp.size.y / bbSize.y); @@ -25,9 +34,9 @@ export function computeSubLayoutMatrix(comp: IComp, compDef: ICompDef, subL } // We get the sub-schematic from the in-editor snapshot (i.e. if it has edits), if available. -// Otherwise we get it from the comp library +// Otherwise we get it from the comp library (or schematic library). export function getCompSubSchematic(editorState: IEditorState, comp: IComp): ISchematic | null { - if (!comp.hasSubSchematic) { + if (!comp.hasSubSchematic && !comp.subSchematicId) { return null; } @@ -38,6 +47,10 @@ export function getCompSubSchematic(editorState: IEditorState, comp: IComp): ISc return subComp; } + if (comp.subSchematicId) { + return editorState.schematicLibrary.getSchematic(comp.subSchematicId)?.model ?? null; + } + let compDef = editorState.compLibrary.getCompDef(comp.defId); return compDef?.subLayout?.layout ?? null; } @@ -71,3 +84,30 @@ export function getParentCompsFromId(editorState: IEditorState, refId: string): return parentComps; } + +export function getCompFromRef(editorState: IEditorState, refId: string): IComp | null { + let parts = refId.split('|'); + let snapshot = editorState.snapshotTemp ?? editorState.snapshot; + let schematic: ISchematic = snapshot; + + for (let partId = 0; partId < parts.length - 1; partId++) { + let part = parts[partId]; + + let comp = schematic.comps.find(c => c.id === part); + + if (!comp) { + return null; + } + + let subSchematic = getCompSubSchematic(editorState, comp); + + if (!subSchematic) { + return null; + } + + schematic = subSchematic; + } + + let lastPartId = parts[parts.length - 1]; + return schematic.comps.find(c => c.id === lastPartId) ?? null; +} diff --git a/src/cpu/comps/CompBuilder.tsx b/src/cpu/comps/CompBuilder.tsx index fc4ef86..91e42a3 100644 --- a/src/cpu/comps/CompBuilder.tsx +++ b/src/cpu/comps/CompBuilder.tsx @@ -184,7 +184,7 @@ export class CompLibrary { if (!compDef) { return; } - comp.name = compDef.name; + comp.name ??= compDef.name; comp.ports = compDef.ports instanceof Function ? compDef.ports(comp.args, compDef) : compDef.ports; comp.size = compDef.size; comp.hasSubSchematic = !!compDef.subLayout; diff --git a/src/cpu/comps/RiscvInsDecode.tsx b/src/cpu/comps/RiscvInsDecode.tsx index 7536427..e3c8de0 100644 --- a/src/cpu/comps/RiscvInsDecode.tsx +++ b/src/cpu/comps/RiscvInsDecode.tsx @@ -353,6 +353,7 @@ export function ensureUnsigned32Bit(x: number) { function renderInsDecoder({ ctx, comp, exeComp, cvs, styles }: ICompRenderArgs) { + return; if (!exeComp) { return; diff --git a/src/cpu/displayTools/StringEditor.tsx b/src/cpu/displayTools/StringEditor.tsx index fad8216..a05c7d6 100644 --- a/src/cpu/displayTools/StringEditor.tsx +++ b/src/cpu/displayTools/StringEditor.tsx @@ -15,10 +15,10 @@ export const StringEditor: React.FC<{ update(true, ev.target.value); } - return