Skip to content

Commit

Permalink
Start adding CompBoundingBox; fix some bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
bbycroft committed Oct 28, 2023
1 parent 05c7eb7 commit 41b960c
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 22 deletions.
176 changes: 176 additions & 0 deletions src/cpu/CompBoundingBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import React, { useState } from "react";
import { IViewLayoutContext, editLayout, useEditorContext, useViewLayout } from "./Editor";
import { BoundingBox3d, Vec3 } from "../utils/vector";
import { RectCorner, RectSide } from "./comps/SchematicComp";
import clsx from "clsx";
import { IPointerEvent, useCombinedMouseTouchDrag } from "../utils/pointer";
import { assignImm } from "../utils/data";
import { CursorDragOverlay } from "../utils/CursorDragOverlay";

export const CompBoundingBox: React.FC<{

}> = () => {
let { editorState, setEditorState } = useEditorContext();
let viewLayout = useViewLayout();
let snapshot = editorState.snapshotTemp ?? editorState.snapshot;
let compBb = snapshot.compBbox;

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

let tl = prev.min.clone();
let br = prev.max.clone();

let isVertical = side === RectSide.Left || side === RectSide.Right;

if (isVertical) {
if (side === RectSide.Left) { tl.x = dest.x; } else { br.x = dest.x; }
} else {
if (side === RectSide.Top) { tl.y = dest.y; } else { br.y = dest.y; }
}

tl = roundToHalfway(tl);
br = roundToHalfway(br);

return assignImm(layout, { compBbox: new BoundingBox3d(tl, br) });
}));

}

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

let tl = prev.min.clone();
let br = prev.max.clone();

if (corner & RectCorner.IsLeft) { tl.x = dest.x; } else { br.x = dest.x; }
if (corner & RectCorner.IsTop) { tl.y = dest.y; } else { br.y = dest.y; }

tl = roundToHalfway(tl);
br = roundToHalfway(br);

return assignImm(layout, { compBbox: new BoundingBox3d(tl, br) });
}));
}

let scale = viewLayout.mtx.a;
let dirs = [RectSide.Top, RectSide.Right, RectSide.Bottom, RectSide.Left];
let corners = [RectCorner.TopLeft, RectCorner.TopRight, RectCorner.BottomRight, RectCorner.BottomLeft];

return <div style={{ position: 'absolute', transformOrigin: 'top left', transform: `scale(${1/scale})` }}>
{dirs.map(side => <EdgeHitTarget key={side} bb={compBb} side={side} viewLayout={viewLayout} onEdgeDrag={handleEdgeDrag} />)}
{corners.map(corner => <CornerHitTarget key={corner} bb={compBb} corner={corner} viewLayout={viewLayout} onCornerDrag={handleCornerDrag} />)}
</div>;
};

export const EdgeHitTarget: React.FC<{
viewLayout: IViewLayoutContext,
bb: BoundingBox3d;
side: RectSide;
onEdgeDrag: (end: boolean, side: RectSide, dest: Vec3) => void;
}> = ({ bb, side, onEdgeDrag, viewLayout }) => {
let [el, setEl] = useState<HTMLDivElement | null>(null);

let [dragStart, setDragStart] = useCombinedMouseTouchDrag(el, (ev) => {
ev.stopPropagation();
ev.preventDefault();
return 0;
}, (ev, _ds, end) => {
onEdgeDrag(end, side, evToModel(viewLayout, ev));
ev.stopPropagation();
ev.preventDefault();
});

let isVertical = side === RectSide.Left || side === RectSide.Right;
let size = bb.size();
let scale = viewLayout.mtx.a;
let hitWidth = 16;

let transform: string | undefined;
if (isVertical) {
let left = side === RectSide.Left ? bb.min.x : bb.max.x;
transform = `translate(${left * scale}px, ${bb.min.y * scale}px) translateX(-50%)`;
} else {
let top = side === RectSide.Top ? bb.min.y : bb.max.y;
transform = `translate(${bb.min.x * scale}px, ${top * scale}px) translateY(-50%)`;
}

return <div
ref={setEl}
className={clsx("pointer-events-auto absolute")}
style={{
cursor: isVertical ? "ew-resize" : "ns-resize",
transform: transform,
width: isVertical ? `${hitWidth}px` : `${size.x * scale}px`,
height: isVertical ? `${size.y * scale}px` : `${hitWidth}px`,
}}
onMouseDown={setDragStart}
>
{dragStart && <CursorDragOverlay className={isVertical ? "cursor-ew-resize" : "cursor-ns-resize"} />}
</div>;
};

export const CornerHitTarget: React.FC<{
viewLayout: IViewLayoutContext,
bb: BoundingBox3d;
corner: RectCorner;
onCornerDrag: (end: boolean, corner: RectCorner, dest: Vec3) => void;
}> = ({ bb, corner, viewLayout, onCornerDrag }) => {
let [el, setEl] = useState<HTMLDivElement | null>(null);

let [dragStart, setDragStart] = useCombinedMouseTouchDrag(el, (ev) => {
ev.stopPropagation();
ev.preventDefault();
return 0;
}, (ev, _ds, end) => {
onCornerDrag(end, corner, evToModel(viewLayout, ev));
ev.stopPropagation();
ev.preventDefault();
});

let isMainDiag = corner === RectCorner.TopLeft || corner === RectCorner.BottomRight;

let hitWidth = 16;
let left = (corner & RectCorner.IsLeft) ? bb.min.x : bb.max.x;
let top = (corner & RectCorner.IsTop) ? bb.min.y : bb.max.y;
let scale = viewLayout.mtx.a;

return <div
ref={setEl}
className={clsx("pointer-events-auto absolute")}
style={{
cursor: isMainDiag ? "nwse-resize" : "nesw-resize",
transform: `translate(${left * scale}px, ${top * scale}px) translate(-50%, -50%)`,
width: `${hitWidth}px`,
height: `${hitWidth}px`,
}}
onMouseDown={setDragStart}
>
{dragStart && <CursorDragOverlay className={isMainDiag ? "cursor-nwse-resize" : "cursor-nesw-resize"} />}
</div>;
};

function evToScreen(viewLayout: IViewLayoutContext, ev: IPointerEvent) {
let bcr = viewLayout.el.getBoundingClientRect();
return new Vec3(ev.clientX - bcr.left, ev.clientY - bcr.top);
}

function evToModel(viewLayout: IViewLayoutContext, ev: IPointerEvent) {
return screenToModel(viewLayout, evToScreen(viewLayout, ev));
}

function screenToModel(viewLayout: IViewLayoutContext, screenPos: Vec3) {
return viewLayout.mtx.mulVec3Inv(screenPos);
}

function modelToScreen(viewLayout: IViewLayoutContext, modelPos: Vec3) {
return viewLayout.mtx.mulVec3(modelPos);
}

function roundToHalfway(a: Vec3) {
return new Vec3(
Math.round(a.x - 0.5) + 0.5,
Math.round(a.y - 0.5) + 0.5);
}
29 changes: 27 additions & 2 deletions src/cpu/CpuCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AffineMat2d } from "../utils/AffineMat2d";
import { IDragStart } from "../utils/pointer";
import { assignImm, getOrAddToMap, isNil, isNotNil } from "../utils/data";
import { EditorContext, IEditorContext, IViewLayoutContext, ViewLayoutContext } from "./Editor";
import { RefType, IComp, PortType, ICompPort, ICanvasState, IEditorState, IHitTest, IExeSystem, ICompRenderArgs, ISchematic, ToolbarTypes } from "./CpuModel";
import { RefType, IComp, PortType, ICompPort, ICanvasState, IEditorState, IHitTest, IExeSystem, ICompRenderArgs, ISchematic, ToolbarTypes, IEditSnapshot } from "./CpuModel";
import { createExecutionModel, stepExecutionCombinatorial } from "./CpuExecution";
import { CompLibraryView } from "./CompLibraryView";
import { CompExampleView } from "./CompExampleView";
Expand All @@ -24,6 +24,7 @@ import { computeSubLayoutMatrix } from "./SubSchematics";
import { computeModelBoundingBox, computeZoomExtentMatrix, createCpuEditorState } from "./ModelHelpers";
import { MainToolbar } from "./toolbars/CpuToolbars";
import { SharedContextContext, createSharedContext } from "./library/SharedContext";
import { CompBoundingBox } from "./CompBoundingBox";

interface ICanvasDragState {
mtx: AffineMat2d;
Expand Down Expand Up @@ -211,7 +212,7 @@ export const CpuCanvas: React.FC<{
let singleElRef = editorState.snapshot.selected.length === 1 ? editorState.snapshot.selected[0] : null;

function getCompDomElements(schematic: ISchematic, idPrefix: string) {
return schematic.comps
let comps = schematic.comps
.map(comp => {
let def = editorState.compLibrary.getCompDef(comp.defId)!;
return (def.renderDom || def.subLayout) && cvsState ? {
Expand Down Expand Up @@ -250,7 +251,13 @@ export const CpuCanvas: React.FC<{
}) ?? null}
{subLayoutDom}
</React.Fragment>;

});

return <>
{comps}
<CompBoundingBox />
</>;
}

let compDivs = getCompDomElements(editorState.snapshotTemp ?? editorState.snapshot, '');
Expand Down Expand Up @@ -297,6 +304,7 @@ export const CpuCanvas: React.FC<{

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);

Expand Down Expand Up @@ -417,6 +425,8 @@ function renderCpu(cvs: ICanvasState, editorState: IEditorState, layout: ISchema
ctx.restore();

renderSelectRegion(cvs, editorState, idPrefix);

renderComponentBoundingBox(cvs, editorState, snapshot, idPrefix);
}


Expand Down Expand Up @@ -531,3 +541,18 @@ function renderCompPort(cvs: ICanvasState, editorState: IEditorState, comp: ICom
}
}

function renderComponentBoundingBox(cvs: ICanvasState, editorState: IEditorState, layout: IEditSnapshot, idPrefix: string) {
let ctx = cvs.ctx;
ctx.save();

let bb = layout.compBbox;
let size = bb.size();
ctx.beginPath();
ctx.rect(bb.min.x, bb.min.y, size.x, size.y);

ctx.lineWidth = 1 * cvs.scale;
ctx.strokeStyle = "#000";
ctx.stroke();

ctx.restore();
}
1 change: 1 addition & 0 deletions src/cpu/CpuModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ export enum PortType {
export interface ISchematic {
comps: IComp[];
wires: IWireGraph[];
compBbox: BoundingBox3d;
}

export interface IEditSnapshot {
Expand Down
4 changes: 2 additions & 2 deletions src/cpu/ImportExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export function importData(str: string): IImportResult {

}

let schematic = { comps, wires };
let schematic: ISchematic = { comps, wires, compBbox: new BoundingBox3d() };

let outStr = exportData(schematic);

Expand All @@ -256,7 +256,7 @@ export function importData(str: string): IImportResult {
}

if (!res.issues) {
res.schematic = { comps, wires };
res.schematic = schematic;
}

return res;
Expand Down
14 changes: 10 additions & 4 deletions src/cpu/ModelHelpers.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { AffineMat2d } from "../utils/AffineMat2d";
import { BoundingBox3d, Vec3 } from "../utils/vector";
import { IEditSnapshot, IEditorState } from "./CpuModel";
import { buildCompLibrary } from "./comps/builtins";
import { compPortDefId } from "./comps/CompPort";
import { ISharedContext, createSharedContext } from "./library/SharedContext";
import { SchematicLibrary } from "./schematics/SchematicLibrary";

export interface IBoundingBoxOptions {
excludePorts?: boolean;
}

export function computeModelBoundingBox(model: IEditSnapshot): BoundingBox3d {
export function computeModelBoundingBox(model: IEditSnapshot, options?: IBoundingBoxOptions): BoundingBox3d {
let modelBbb = new BoundingBox3d();

for (let c of model.comps) {
if (options?.excludePorts && c.defId === compPortDefId) {
continue;
}

modelBbb.addInPlace(c.pos);
modelBbb.addInPlace(c.pos.add(c.size));
}
Expand All @@ -18,7 +24,7 @@ export function computeModelBoundingBox(model: IEditSnapshot): BoundingBox3d {
modelBbb.addInPlace(n.pos);
}
}
if (model.compBbox) {
if (model.compBbox && !options?.excludePorts) {
modelBbb.combineInPlace(model.compBbox);
}

Expand Down
4 changes: 2 additions & 2 deletions src/cpu/WireRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ export function renderWire(cvs: ICanvasState, editorState: IEditorState, wire: I
} else {
ctx.strokeStyle = '#000';
}
ctx.lineWidth = (width - 1) * cvs.scale;
ctx.filter = 'blur(4px)';
ctx.lineWidth = width * cvs.scale;
ctx.filter = 'blur(3px)';
ctx.moveTo(node0.pos.x, node0.pos.y);
ctx.lineTo(node1.pos.x, node1.pos.y);
ctx.stroke();
Expand Down
10 changes: 10 additions & 0 deletions src/cpu/comps/SchematicComp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ export enum RectSide {
Left,
}

export enum RectCorner {
TopLeft = 1,
TopRight = 2,
BottomRight = 4,
BottomLeft = 8,

IsLeft = TopLeft | BottomLeft,
IsTop = TopLeft | TopRight,
}

function createInsetGradient(ctx: CanvasRenderingContext2D, bb: BoundingBox3d, inset: number, colorOuter: string) {
let w = bb.max.x - bb.min.x;
let h = bb.max.y - bb.min.y;
Expand Down
Loading

0 comments on commit 41b960c

Please sign in to comment.