Skip to content

Commit

Permalink
Initial sub-schematic support for builtins
Browse files Browse the repository at this point in the history
  • Loading branch information
bbycroft committed Oct 30, 2023
1 parent 41b960c commit 235f1b6
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/cpu/CanvasEventHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 + '|');
Expand Down
11 changes: 5 additions & 6 deletions src/cpu/CanvasRenderHelpers.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -26,13 +26,12 @@ export function makeCanvasFont(fontSize: number, fontType: FontType = FontType.D

export interface ICanvasGridState {
tileCanvases: Map<string, HTMLCanvasElement>;
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

Expand Down
1 change: 1 addition & 0 deletions src/cpu/Clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
75 changes: 75 additions & 0 deletions src/cpu/CompDetails.tsx
Original file line number Diff line number Diff line change
@@ -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 <div className="flex flex-col flex-1">
{numSelected === 0 && <div>No component selected</div>}
{numSelected === 1 && singleComp && compDef && <div>
<div className="mt-2 mb-2 mx-2"><b>{singleComp.name}</b></div>
<div className="mx-2">
<EditKvp label={'Id'}><code>{singleComp.id}</code></EditKvp>
<EditKvp label={'Def Id'}><code>{singleComp.defId}</code></EditKvp>
<EditKvp label={'Name'}><StringEditor className="bg-slate-100 rounded flex-1" value={singleComp.name} update={handleNameUpdate} /></EditKvp>
<EditKvp label={'Ext Id'}><StringEditor className="bg-slate-100 rounded font-mono flex-1" value={singleComp.extId ?? ''} update={handleExtIdUpdate} /></EditKvp>
<EditKvp label={'Pos'}>{`${singleComp.pos}`}</EditKvp>
<EditKvp label={'Size'}>{`${singleComp.size}`}</EditKvp>
</div>
<div className="m-2">
<div className="mb-2">Internal Schematic</div>
{!subSchematic && <button
className="rounded border-gray-400 border border-solid py-1 px-2 hover:bg-slate-100"
onClick={handleInternalSchematicAddNew}>
<FontAwesomeIcon icon={faPlus} className="mr-2" />
Add New
</button>}
{singleComp.subSchematicId && <EditKvp label={'Id'}><code>{singleComp.subSchematicId}</code></EditKvp>}
{subSchematic && <EditKvp label={'Name'}><code>{(subSchematic as IEditSnapshot)?.name}</code></EditKvp>}
</div>
</div>}
</div>;
};

export const EditKvp: React.FC<{
label: string;
children?: React.ReactNode;
}> = ({ label, children }) => {
return <div className="flex flex-row items-center my-1">
<div className="w-[5rem] mr-2">{label}</div>
<div className="flex-1">{children}</div>
</div>;
};
2 changes: 1 addition & 1 deletion src/cpu/CompLayoutEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
107 changes: 73 additions & 34 deletions src/cpu/CpuCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 = <div
className={"absolute origin-top-left"}
style={{ transform: `matrix(${subMtx.toTransformParams().join(',')})` }}
>
{getCompDomElements(a.subLayout.layout, idPrefix + a.comp.id + '|')}
{getCompDomElements(schematic, idPrefix + a.comp.id + '|')}
</div>;
}

Expand Down Expand Up @@ -269,44 +275,66 @@ export const CpuCanvas: React.FC<{
return <EditorContext.Provider value={ctx}>
<ViewLayoutContext.Provider value={viewLayout}>
{!readonly && <MainToolbar readonly={readonly} toolbars={toolbars} />}
<div className="relative touch-none flex-1 overflow-hidden">
<canvas className="absolute touch-none w-full h-full" ref={setCanvasEl} />
{cvsState && <CanvasEventHandler cvsState={cvsState}>
<div className={"overflow-hidden absolute left-0 top-0 w-full h-full pointer-events-none"}>
<div
className={"absolute origin-top-left"}
style={{ transform: `matrix(${editorState.mtx.toTransformParams().join(',')})` }}>
{compDivs}
<Resizer className="flex-1 flex flex-row" id={"cpu-tools-right"} defaultFraction={0.9}>
<div className="relative touch-none flex-1 overflow-hidden">
<canvas className="absolute touch-none w-full h-full" ref={setCanvasEl} />
{cvsState && <CanvasEventHandler cvsState={cvsState}>
<div className={"overflow-hidden absolute left-0 top-0 w-full h-full pointer-events-none"}>
<div
className={"absolute origin-top-left"}
style={{ transform: `matrix(${editorState.mtx.toTransformParams().join(',')})` }}>
{compDivs}
</div>
{editorState.transparentComps && <div className="absolute w-full h-full pointer-events-auto top-0 left-0" />}
</div>
{editorState.transparentComps && <div className="absolute w-full h-full pointer-events-auto top-0 left-0" />}
</CanvasEventHandler>}
<div className={s.toolsLeftTop}>
{!readonly && <>
<CompLibraryView />
<CompExampleView />
<SchematicLibraryView />
</>}
{!editorState.snapshotTemp && !editorState.maskHover && <HoverDisplay canvasEl={cvsState?.canvas ?? null} />}
</div>
</CanvasEventHandler>}
<div className={s.toolsLeftTop}>
{!readonly && <>
<CompLibraryView />
<CompExampleView />
<SchematicLibraryView />
</>}
{!editorState.snapshotTemp && !editorState.maskHover && <HoverDisplay canvasEl={cvsState?.canvas ?? null} />}
{readonly && <div className="absolute left-2 top-2 pointer-events-auto">
<MainToolbar readonly={readonly} toolbars={toolbars} />
</div>}
<div className="cls_toolsTopRight absolute top-0 right-0">
{!readonly && <CompLayoutToolbar />}
</div>
{editorState.compLibraryVisible && <LibraryBrowser />}
{children}
</div>
{readonly && <div className="absolute left-2 top-2 pointer-events-auto">
<MainToolbar readonly={readonly} toolbars={toolbars} />
</div>}
<div className="cls_toolsTopRight absolute top-0 right-0">
{!readonly && <CompLayoutToolbar />}
<div className="flex-1 flex flex-col">
<CompDetails />
</div>
{editorState.compLibraryVisible && <LibraryBrowser />}
{children}
</div>
</Resizer>
</ViewLayoutContext.Provider>
</EditorContext.Provider>;
};

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];
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -427,6 +464,8 @@ function renderCpu(cvs: ICanvasState, editorState: IEditorState, layout: ISchema
renderSelectRegion(cvs, editorState, idPrefix);

renderComponentBoundingBox(cvs, editorState, snapshot, idPrefix);

// renderAxes(cvs, editorState);
}


Expand Down
21 changes: 21 additions & 0 deletions src/cpu/CpuModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -247,6 +248,7 @@ export interface IComp<A = any> {
defId: string;
name: string;
extId?: string; // an id that can be referenced "externally"
subSchematicId?: string;
pos: Vec3;
size: Vec3;
ports: ICompPort[];
Expand Down Expand Up @@ -294,6 +296,7 @@ export interface IEditSnapshot {
nextWireId: number;
comps: IComp[];
wires: IWireGraph[];
parentCompDefId?: string;

// custom component def
compSize: Vec3;
Expand All @@ -303,11 +306,29 @@ export interface IEditSnapshot {
subComps: Map<string, IEditSchematic>;
}

/**
* 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 {
Expand Down
1 change: 1 addition & 0 deletions src/cpu/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function editComp<A>(editCtx: IEditContext, end: boolean, comp: IComp<A>,

let comp2 = layout.comps.find(a => a.id === comp.id) as IComp<A> | null;
if (!comp2) {
console.log('unable to edit comp!!');
return layout;
}

Expand Down
2 changes: 1 addition & 1 deletion src/cpu/HoverDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading

0 comments on commit 235f1b6

Please sign in to comment.