Skip to content

Commit

Permalink
Start working on sub-schematic editing
Browse files Browse the repository at this point in the history
  • Loading branch information
bbycroft committed Nov 1, 2023
1 parent fcf7b8a commit ebdc8f4
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 65 deletions.
12 changes: 6 additions & 6 deletions src/cpu/CanvasEventHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { isKeyWithModifiers, KeyboardOrder, Modifiers, useGlobalKeyboard } from
import { useCombinedMouseTouchDrag, useTouchEvents } from '../utils/pointer';
import { BoundingBox3d, projectOntoVector, segmentNearestPoint, Vec3 } from '../utils/vector';
import { ICanvasState, IEditSnapshot, IEditorState, IElRef, IHitTest, ISchematic, ISegment, IWireGraph, RefType } from './CpuModel';
import { editLayout, useEditorContext } from './Editor';
import { editSnapshot, useEditorContext } from './Editor';
import { fixWire, wireToGraph, applyWires, checkWires, copyWireGraph, EPSILON, dragSegment, moveSelectedComponents, iterWireGraphSegments, refToString, wireUnlinkNodes, repackGraphIds, splitIntoIslands } from './Wire';
import s from './CpuCanvas.module.scss';
import { CursorDragOverlay } from '../utils/CursorDragOverlay';
Expand Down Expand Up @@ -44,7 +44,7 @@ export const CanvasEventHandler: React.FC<{
}

if (ev.key === "Delete") {
setEditorState(editLayout(true, layout => {
setEditorState(editSnapshot(true, layout => {

let refStrs = new Set(layout.selected.map(s => refToString(s)));
function selectionHasRef(id: string, type: RefType) {
Expand Down Expand Up @@ -247,15 +247,15 @@ export const CanvasEventHandler: React.FC<{

function handleSelectionDrag(end: boolean, origModelPos: Vec3, newModelPos: Vec3) {

setEditorState(editLayout(end, layout => {
setEditorState(editSnapshot(end, layout => {
let deltaPos = newModelPos.sub(origModelPos);
let snappedDelta = snapToGrid(deltaPos);
return moveSelectedComponents(layout, snappedDelta);
}));
}

function handleWireCreateDrag(end: boolean, ref: IElRef, origModelPos: Vec3, newModelPos: Vec3) {
setEditorState(editLayout(end, layout => {
setEditorState(editSnapshot(end, layout => {
let startComp = layout.comps.find(c => c.id === ref.id);
if (!startComp) {
console.log(`WARN: handleWireCreateDrag: comp '${ref.id}' not found`);
Expand Down Expand Up @@ -310,7 +310,7 @@ export const CanvasEventHandler: React.FC<{
do a shorten + single extend in opposite direction, i.e. keep the elbow, rather than create a T junction
*/
function handleWireExtendDrag(end: boolean, ref: IElRef, origModelPos: Vec3, newModelPos: Vec3) {
setEditorState(editLayout(end, function handleWireExtendDrag(layout) {
setEditorState(editSnapshot(end, function handleWireExtendDrag(layout) {
checkWires(editorState.snapshot.wires, 'handleWireExtendDrag (pre edit)');
let wireIdx = editorState.snapshot.wires.findIndex(w => w.id === ref.id);
if (wireIdx === -1) {
Expand Down Expand Up @@ -403,7 +403,7 @@ export const CanvasEventHandler: React.FC<{

function handleWireDrag(end: boolean, ref: IElRef, origModelPos: Vec3, newModelPos: Vec3) {

setEditorState(editLayout(end, (layout, state) => {
setEditorState(editSnapshot(end, (layout, state) => {
let wireIdx = state.snapshot.wires.findIndex(w => w.id === ref.id);
if (wireIdx === -1) {
console.log(`WARN: handleWireDrag: wire ${ref.id} not found`)
Expand Down
6 changes: 3 additions & 3 deletions src/cpu/Clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StateSetter, getOrAddToMap } from "../utils/data";
import { Vec3 } from "../utils/vector";
import { IEditorState, RefType, IElRef, IWireGraph, ISchematic, IEditSnapshot } from "./CpuModel";
import { editLayout } from "./Editor";
import { editSnapshot } from "./Editor";
import { ILSState, exportData, importData, schematicToLsState } from "./ImportExport";
import { deleteSelection } from "./Selection";
import { copyWireGraph, repackGraphIds, splitIntoIslands } from "./Wire";
Expand All @@ -10,7 +10,7 @@ import { CompLibrary } from "./comps/CompBuilder";
export function cutSelection(ev: KeyboardEvent, editorState: IEditorState, setEditorState: StateSetter<IEditorState>) {
let schematic = selectionToSchematic(editorState);
writeToClipboard(exportData(schematic));
setEditorState(editLayout(true, deleteSelection));
setEditorState(editSnapshot(true, deleteSelection));
}

export function copySelection(ev: KeyboardEvent, editorState: IEditorState, setEditorState: StateSetter<IEditorState>) {
Expand Down Expand Up @@ -40,7 +40,7 @@ export function pasteSelection(ev: KeyboardEvent, editorState: IEditorState, set
// maybe move the selection with the mouse, and require a click to place it?
// e.g. say we want to duplicate vertically, and link things up automatically

setEditorState(editLayout(true, (layout, editorState) => {
setEditorState(editSnapshot(true, (layout, editorState) => {
return mergeInSchematic(layout, res.schematic!, editorState.compLibrary);
}));
}
Expand Down
6 changes: 3 additions & 3 deletions src/cpu/CompBoundingBox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { IViewLayoutContext, editLayout, useEditorContext, useViewLayout } from "./Editor";
import { IViewLayoutContext, editSnapshot, useEditorContext, useViewLayout } from "./Editor";
import { BoundingBox3d, Vec3 } from "../utils/vector";
import { RectCorner, RectSide } from "./comps/SchematicComp";
import clsx from "clsx";
Expand All @@ -16,7 +16,7 @@ export const CompBoundingBox: React.FC<{
let compBb = snapshot.compBbox;

function handleEdgeDrag(end: boolean, side: RectSide, dest: Vec3) {
setEditorState(editLayout(end, (layout) => {
setEditorState(editSnapshot(end, (layout) => {
let prev = layout.compBbox;

let tl = prev.min.clone();
Expand All @@ -39,7 +39,7 @@ export const CompBoundingBox: React.FC<{
}

function handleCornerDrag(end: boolean, corner: RectCorner, dest: Vec3) {
setEditorState(editLayout(end, (layout) => {
setEditorState(editSnapshot(end, (layout) => {
let prev = layout.compBbox;

let tl = prev.min.clone();
Expand Down
10 changes: 5 additions & 5 deletions src/cpu/CompLayoutEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import React, { memo, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
import { ViewLayoutContext, editLayout, editLayoutDirect, useEditorContext, useViewLayout } from './Editor';
import { ViewLayoutContext, editSnapshot, editSnapshotDirect, useEditorContext, useViewLayout } from './Editor';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCaretRight, faPlus } from '@fortawesome/free-solid-svg-icons';
import { Gripper, ICompPortConfig, compPortDefId } from './comps/CompPort';
Expand Down Expand Up @@ -68,7 +68,7 @@ export const CompLayoutToolbar: React.FC<{
function onCreateEditClicked(ev: React.MouseEvent) {
setIsExpanded(a => !a);
if (!hasComponent) {
setEditorState(editLayout(true, (snap, state) => {
setEditorState(editSnapshot(true, (snap, state) => {
return assignImm(snap, {
compSize: new Vec3(4, 4),
});
Expand Down Expand Up @@ -235,7 +235,7 @@ export const CompLayoutEditor: React.FC<{
let newPorts = [...snapshot.compPorts, ...autogenPorts];

if (autogenPorts.length > 0) {
setEditorState(editLayoutDirect((snap) => {
setEditorState(editSnapshotDirect((snap) => {
return assignImm(snap, { compPorts: newPorts });
}));
}
Expand Down Expand Up @@ -321,7 +321,7 @@ export const CompBoxEditor: React.FC<{

function handleResize(end: boolean, pos: Vec3, size: Vec3) {
setPos(end, pos);
setEditorState(editLayout(end, snap => {
setEditorState(editSnapshot(end, snap => {
return resizeCompBox(snap, size);
// return assignImm(snap, { compSize: size });
}));
Expand Down Expand Up @@ -380,7 +380,7 @@ export const CompPortEditor: React.FC<{
let delta = evToModel(ev).sub(evToModel(ds));
let newPos = ds.data.add(delta);
setDraggingPortIdx(end ? null : portIdx);
setEditorState(editLayout(end, snap => {
setEditorState(editSnapshot(end, snap => {
return movePortToNewLocation(snap, portIdx, newPos);
}));
ev.stopPropagation();
Expand Down
4 changes: 2 additions & 2 deletions src/cpu/CompLibraryView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { editLayout, useEditorContext } from "./Editor";
import { editSnapshot, useEditorContext } from "./Editor";
import s from "./CompLibraryView.module.scss";
import { ICompDef } from "./comps/CompBuilder";
import { assignImm } from "../utils/data";
Expand All @@ -15,7 +15,7 @@ export const CompLibraryView: React.FC = () => {
let [, setDragStart] = useGlobalDrag<number>(function handleMove(ev, ds, end) {
setEditorState(a => {
if (a.dragCreateComp?.applyFunc) {
a = editLayout(end, a.dragCreateComp.applyFunc)(a);
a = editSnapshot(end, a.dragCreateComp.applyFunc)(a);
}
if (end) {
a = assignImm(a, { dragCreateComp: undefined });
Expand Down
4 changes: 2 additions & 2 deletions src/cpu/CpuModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export interface IEditSnapshot {
// what's the key here?
// for builtins with "subSchematicId", want the key to be the same
// for custom components, also want this?
subComps: Map<string, IEditSchematic>;
subSchematics: Record<string, IEditSchematic>;
}

/**
Expand All @@ -328,7 +328,7 @@ export interface IEditSnapshot {
* presence of the port in the schematic.
*/

interface IEditSchematic {
export interface IEditSchematic {
nextCompId: number;
nextWireId: number;
comps: IComp[];
Expand Down
118 changes: 104 additions & 14 deletions src/cpu/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { assignImm, StateSetter } from '../utils/data';
import { IComp, IEditContext, IEditSnapshot, IEditorState, IExeSystem } from './CpuModel';
import { assignImm, isNotNil, StateSetter } from '../utils/data';
import { IComp, IEditContext, IEditSchematic, IEditSnapshot, IEditorState, IExeSystem, ISchematic } from './CpuModel';
import { updateWiresForComp } from './Wire';
import { AffineMat2d } from '../utils/AffineMat2d';
import { Subscriptions } from '../utils/hooks';
import { getCompSubSchematicForSnapshot, getParentCompsFromId } from './SubSchematics';

export enum PortHandling {
Detach, // e.g. for rotating a component, the wire will need to be manually re-attached
Expand All @@ -19,36 +20,87 @@ export function editCompConfig<A>(editCtx: IEditContext, end: boolean, comp: ICo
return editComp(editCtx, end, comp, a => assignImm(a, { args: updateConfig(a.args) }), compEditArgs);
}

/*
## Editing Sub-Schematics ##
* We have a good system of refs, where we refer to a comp by its id and its parent comp ids
* But then we need to edit those refs
* We can find the comp, as well as extract the idPrefix
* This runs into trouble when we're operating on a selection that is cross-schematic
* Probably want some way to figure out the primary schematic, or disallow cross-schematic selections
*/

export function editComp<A>(editCtx: IEditContext, end: boolean, comp: IComp<A>, updateComp: (comp: IComp<A>) => IComp<A>, compEditArgs?: ICompEditArgs) {
return editLayout(end, (layout, state) => {
return editSubSnapshot(editCtx, end, (snapshot, state) => {
console.log(`editing comp ${comp.id} (idPrefix = ${editCtx.idPrefix})`);

if (editCtx.idPrefix) {
return layout; // TODO
let parentComps = getParentCompsFromId(state, editCtx.idPrefix + comp.id);
let lastParent = parentComps[parentComps.length - 1];
let schematicId = lastParent.hasSubSchematic ? lastParent.defId : lastParent.subSchematicId;

let subSchematic = getCompSubSchematicForSnapshot(state.sharedContext, snapshot, lastParent);

if (!subSchematic || !schematicId) {
console.log(`failed to find schematic id of parent comp (idPrefix = ${editCtx.idPrefix}; comp.id = ${comp.id}), parents = `, parentComps);
return snapshot;
}

console.log(`updating comp.id = ${comp.id} in schematic ${schematicId} (idPrefix = ${editCtx.idPrefix})`);

let comp2 = subSchematic.comps.find(a => a.id === comp.id) as IComp<A> | null;
if (!comp2) {
console.log(`unable to find comp.id = ${comp.id} in schematic ${schematicId} (idPrefix = ${editCtx.idPrefix})`);
return snapshot;
}

let comp3 = updateComp(comp2);
if (comp3 === comp2) {
return snapshot;
}

let subSchematic2 = ensureEditSchematic(assignImm(subSchematic, { comps: subSchematic.comps.map(a => a.id === comp.id ? comp3! : a) }));
state.compLibrary.updateCompFromDef(comp3);
subSchematic2 = updateWiresForComp(subSchematic2, comp3, compEditArgs?.portHandling ?? PortHandling.Move);

let res = assignImm(snapshot, {
subSchematics: assignImm(snapshot.subSchematics, { [schematicId]: subSchematic2 })
});

console.log('schematic:', subSchematic2);
console.log('res: ', res);

return res;
}

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

let comp3 = updateComp(comp2);
if (comp3 === comp2) {
return layout;
return snapshot;
}

layout = assignImm(layout, { comps: layout.comps.map(a => a.id === comp.id ? comp3! : a) });
snapshot = assignImm(snapshot, { comps: snapshot.comps.map(a => a.id === comp.id ? comp3! : a) });
state.compLibrary.updateCompFromDef(comp3);

layout = updateWiresForComp(layout, comp3, compEditArgs?.portHandling ?? PortHandling.Move);
snapshot = updateWiresForComp(snapshot, comp3, compEditArgs?.portHandling ?? PortHandling.Move);

return layout;
return snapshot;
});
}

export function editLayout(end: boolean, updateLayout: (element: IEditSnapshot, state: IEditorState) => IEditSnapshot) {
export function editSubSnapshot(editCtx: IEditContext, end: boolean, updateSnapshot: (element: IEditSnapshot, state: IEditorState) => IEditSnapshot) {
return (state: IEditorState) => {
let newSnapshot = updateLayout(state.snapshot, state);

// TODO: get the subSchematicId from the editCtx, and update the subSchematic instead of the main schematic

let newSnapshot = updateSnapshot(state.snapshot, state);

if (end) {
if (newSnapshot === state.snapshot) {
Expand All @@ -69,13 +121,51 @@ export function editLayout(end: boolean, updateLayout: (element: IEditSnapshot,
};
}

export function editLayoutDirect(updateLayout: (element: IEditSnapshot, state: IEditorState) => IEditSnapshot) {
export function editSnapshot(end: boolean, updateSnapshot: (element: IEditSnapshot, state: IEditorState) => IEditSnapshot) {
return (state: IEditorState) => {
let changed = updateLayout(state.snapshot, state);
let newSnapshot = updateSnapshot(state.snapshot, state);

if (end) {
if (newSnapshot === state.snapshot) {
return assignImm(state, { snapshotTemp: null });
}

state = assignImm(state, {
snapshot: newSnapshot,
snapshotTemp: null,
undoStack: [...state.undoStack, state.snapshot],
redoStack: [],
});
} else {
state = assignImm(state, { snapshotTemp: newSnapshot });
}

return state;
};
}

export function editSnapshotDirect(updateSnapshot: (element: IEditSnapshot, state: IEditorState) => IEditSnapshot) {
return (state: IEditorState) => {
let changed = updateSnapshot(state.snapshot, state);
return assignImm(state, { snapshot: changed, snapshotTemp: null });
};
}

export function ensureEditSchematic(schematic: ISchematic | IEditSchematic): IEditSchematic {
if (isEditSchematic(schematic)) {
return schematic;
}
return assignImm(schematic as IEditSchematic, {
nextCompId: schematic.comps.reduce((max, c) => Math.max(max, parseInt(c.id)), 0) + 1,
nextWireId: schematic.wires.reduce((max, c) => Math.max(max, parseInt(c.id)), 0) + 1,
});
}

export function isEditSchematic(schematic: ISchematic | IEditSchematic): schematic is IEditSchematic {
return isNotNil((schematic as IEditSchematic).nextCompId);
}


export function undoAction(state: IEditorState) {
if (state.undoStack.length === 0) {
return state;
Expand Down
17 changes: 0 additions & 17 deletions src/cpu/ImportExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,23 +297,6 @@ export function hydrateFromLS(ls: Partial<ILSState> | undefined): ILSState {
};
}

export function createInitialEditSnapshot(): IEditSnapshot {
return {
name: '',
comps: [],
wires: [],
nextCompId: 0,
nextWireId: 0,
selected: [],

compPorts: [],
compSize: new Vec3(0, 0),
compBbox: new BoundingBox3d(),

subComps: new Map(),
};
}

export function wiresFromLsState(layoutBase: IEditSnapshot, ls: ILSState, compLibrary: CompLibrary): IEditSnapshot {

let wireIdx = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/cpu/ModelHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,6 @@ export function constructEditSnapshot(): IEditSnapshot {
compSize: new Vec3(0, 0),
compBbox: new BoundingBox3d(),

subComps: new Map(),
subSchematics: {},
};
}
2 changes: 1 addition & 1 deletion src/cpu/SubSchematics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function getCompSubSchematicForSnapshot(sharedContext: ISharedContext, sn
}

if (comp.subSchematicId) {
let editSchematic = snapshot.subComps.get(comp.subSchematicId);
let editSchematic = snapshot.subSchematics[comp.subSchematicId];
if (editSchematic) {
return editSchematic;
}
Expand Down
Loading

0 comments on commit ebdc8f4

Please sign in to comment.