Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model refactor API SelectionPopup [1] #1284

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 27 additions & 4 deletions src/app/core/internal/impl/CircuitInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ import {CircuitDocument, ReadonlyCircuitDocument} from "./CircuitDocument
import {FastCircuitDiff, FastCircuitDiffBuilder} from "./FastCircuitDiff";


export type InternalEvent = {
type: "CircuitOp";
diff: FastCircuitDiff;
} | {
type: "CameraOp";
diff: {
dx: number;
dy: number;
dz: number;
};
}


// CircuitInternal is a low-level session for editing a circuit. It encapsulates the CircuitDocument and the CircuitLog
// to provide a transaction mechanism for building LogEntries. It also provides a higher-level interface for producing
// legal CircuitOps based on the desired change and the CircuitDocument state.
Expand All @@ -26,7 +39,7 @@ import {FastCircuitDiff, FastCircuitDiffBuilder} from "./FastCircuitDiff
// EXCEPTION POLICY:
// ERRORS: thrown during unexpected, exceptional, irrecoverable scenarios
// OTHERWISE: The "Result" and "Option" types are used to communicate success/failure.
export class CircuitInternal extends Observable<FastCircuitDiff> {
export class CircuitInternal extends Observable<InternalEvent> {
private readonly mutableDoc: CircuitDocument;
public get doc(): ReadonlyCircuitDocument {
return this.mutableDoc;
Expand Down Expand Up @@ -85,7 +98,7 @@ export class CircuitInternal extends Observable<FastCircuitDiff> {
private publishDiffEvent() {
const diff = this.diffBuilder.build();
this.diffBuilder = new FastCircuitDiffBuilder();
this.publish(diff);
this.publish({ type: "CircuitOp", diff });
}

private applyOp(op: CircuitOp): Result {
Expand Down Expand Up @@ -303,12 +316,22 @@ export class CircuitInternal extends Observable<FastCircuitDiff> {
}

public setCameraProps(props: Partial<Schema.Camera>) {
const dx = (props.x ?? this.camera.x) - this.camera.x;
const dy = (props.y ?? this.camera.y) - this.camera.y;
const dz = (props.zoom ?? this.camera.zoom) - this.camera.zoom;

// No change, no need to emit event
if (dx === 0 && dy === 0 && dz === 0)
LeonMontealegre marked this conversation as resolved.
Show resolved Hide resolved
return;

this.camera.x = (props.x ?? this.camera.x);
this.camera.y = (props.y ?? this.camera.y);
this.camera.zoom = (props.zoom ?? this.camera.zoom);

// TODO[model_refactor_api](idk) The camera changing is a very different kind of event than the others here.
this.publish((new FastCircuitDiffBuilder()).build());
this.publish({
type: "CameraOp",
diff: { dx, dy, dz },
});
}

public getMetadata(): Readonly<Schema.CircuitMetadata> {
Expand Down
20 changes: 12 additions & 8 deletions src/app/core/internal/view/CircuitView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,27 +111,33 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
this.scheduler.setBlocked(true);

this.circuit.subscribe((ev) => {
if (ev.type === "CameraOp") {
this.cameraMat = this.calcCameraMat();
this.scheduler.requestRender();
return;
}
// TODO[model_refactor_api](leon) - use events better, i.e. how do we collect the diffs until the next
// render cycle or query for the dirty object(s)?
const diff = ev.diff;

// Mark all added/removed component dirty
for (const compID of ev.addedComponents)
for (const compID of diff.addedComponents)
this.dirtyComponents.add(compID);
for (const compID of ev.removedComponents)
for (const compID of diff.removedComponents)
this.dirtyComponents.add(compID);

// Mark all components w/ changed ports dirty
for (const compID of ev.portsChanged)
for (const compID of diff.portsChanged)
this.dirtyComponents.add(compID);

// Mark all added/removed wires dirty
for (const wireID of ev.addedWires)
for (const wireID of diff.addedWires)
this.dirtyWires.add(wireID);
for (const wireID of ev.removedWires)
for (const wireID of diff.removedWires)
this.dirtyWires.add(wireID);

// Mark all changed obj props dirty
for (const [id, props] of ev.propsChanged) {
for (const [id, props] of diff.propsChanged) {
if (circuit.doc.hasComp(id)) {
this.dirtyComponents.add(id);

Expand All @@ -150,8 +156,6 @@ export abstract class CircuitView extends Observable<{ renderer: RenderHelper }>
}
}

this.cameraMat = this.calcCameraMat();

this.scheduler.requestRender();
});

Expand Down
16 changes: 14 additions & 2 deletions src/app/core/public/api/Camera.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import {Vector} from "Vector";
import {Margin} from "math/Rect";

import {Obj} from "./Obj";
import {Obj} from "./Obj";
import {Observable} from "./Observable";


export interface Camera {
export type CameraEvent = {
type: "dragStart";
} | {
type: "dragEnd";
} | {
type: "change";
dx: number;
dy: number;
dz: number;
}

export interface Camera extends Observable<CameraEvent> {
cx: number;
cy: number;
pos: Vector;
Expand Down
7 changes: 6 additions & 1 deletion src/app/core/public/api/Circuit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ import {Obj} from "./Obj";
import {Port} from "./Port";
import {Wire} from "./Wire";
import {Selections} from "./Selections";
import {Observable} from "./Observable";


export type {CircuitMetadata} from "core/schema/CircuitMetadata";

export interface Circuit {
// export type CircuitEvent = {
// type: "any";
// }

export interface Circuit {// extends Observable<CircuitEvent> {
beginTransaction(): void;
commitTransaction(): void;
cancelTransaction(): void;
Expand Down
5 changes: 5 additions & 0 deletions src/app/core/public/api/Observable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Observable<Event extends { type: string }> {
observe(listener: (ev: Event) => void): () => void;

emit(ev: Event): void;
}
14 changes: 10 additions & 4 deletions src/app/core/public/api/Selections.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import {Vector} from "Vector";

import {Component} from "./Component";
import {Obj} from "./Obj";
import {Wire} from "./Wire";
import {Component} from "./Component";
import {Obj} from "./Obj";
import {Wire} from "./Wire";
import {Observable} from "./Observable";


export interface Selections {
export type SelectionsEvent = {
type: "numSelectionsChanged";
newAmt: number;
}

export interface Selections extends Observable<SelectionsEvent> {
readonly length: number;
readonly isEmpty: boolean;

Expand Down
17 changes: 14 additions & 3 deletions src/app/core/public/api/impl/Camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ import {V, Vector} from "Vector";
import {Clamp} from "math/MathUtils";
import {Margin} from "math/Rect";

import {Camera} from "../Camera";
import {extend} from "core/utils/Functions";

import {Camera, CameraEvent} from "../Camera";
import {CircuitState, CircuitTypes} from "./CircuitState";
import {ObservableImpl} from "./Observable";


export function CameraImpl<T extends CircuitTypes>({ internal, view }: CircuitState<T>) {
function camera() {
return internal.getCamera();
}

return {
const observable = ObservableImpl<CameraEvent>();

internal.subscribe((ev) => {
if (ev.type !== "CameraOp")
return;
observable.emit({ type: "change", ...ev.diff });
});

return extend(observable, {
set cx(x: number) {
internal.setCameraProps({ x });
},
Expand Down Expand Up @@ -56,5 +67,5 @@ export function CameraImpl<T extends CircuitTypes>({ internal, view }: CircuitSt
zoomToFit(objs: T["Obj[]"], margin?: Margin, padRatio?: number): void {
throw new Error("Unimplemented!");
},
} satisfies Camera;
}) satisfies Camera;
}
9 changes: 7 additions & 2 deletions src/app/core/public/api/impl/Circuit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export function CircuitImpl<CircuitT extends Circuit, T extends CircuitTypes>(st
return state.view.findNearestObj(pos, filter);
}

const camera = CameraImpl(state);
let selections: Selections;

return {
beginTransaction(): void {
state.internal.beginTransaction();
Expand Down Expand Up @@ -75,10 +78,12 @@ export function CircuitImpl<CircuitT extends Circuit, T extends CircuitTypes>(st
},

get camera(): Camera {
return CameraImpl(state);
return camera;
},
get selections(): Selections {
return SelectionsImpl(this, state);
if (!selections)
selections = SelectionsImpl(this, state);
return selections;
},

// Queries
Expand Down
16 changes: 16 additions & 0 deletions src/app/core/public/api/impl/Observable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Observable} from "../Observable";


export function ObservableImpl<Event extends { type: string }>() {
const listeners: Set<(ev: Event) => void> = new Set();

return {
observe(listener: (ev: Event) => void): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
},
emit(ev: Event): void {
listeners.forEach((listener) => listener(ev));
},
} satisfies Observable<Event>;
}
20 changes: 16 additions & 4 deletions src/app/core/public/api/impl/Selections.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {V, Vector} from "Vector";

import {Selections} from "../Selections";
import {Circuit} from "../Circuit";
import {extend} from "core/utils/Functions";

import {Selections, SelectionsEvent} from "../Selections";
import {Circuit} from "../Circuit";

import {CircuitState, CircuitTypes} from "./CircuitState";
import {ObservableImpl} from "./Observable";


export function SelectionsImpl<T extends CircuitTypes>(
Expand All @@ -14,7 +17,16 @@ export function SelectionsImpl<T extends CircuitTypes>(
return selectionsManager.get();
}

return {
const observable = ObservableImpl<SelectionsEvent>();

selectionsManager.subscribe((_) => {
observable.emit({
type: "numSelectionsChanged",
newAmt: selectionsManager.length(),
});
});

return extend(observable, {
get length(): number {
return selections().length;
},
Expand Down Expand Up @@ -64,5 +76,5 @@ export function SelectionsImpl<T extends CircuitTypes>(
return [];
throw new Error("Unimplemented!");
},
} satisfies Selections;
}) satisfies Selections;
}
17 changes: 9 additions & 8 deletions src/site/pages/digital/src/containers/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {useWindowSize} from "shared/utils/hooks/useWindowSize";
// import {HistoryBox} from "shared/containers/HistoryBox";
// import {ImageExporterPopup} from "shared/containers/ImageExporterPopup";
// import {LoginPopup} from "shared/containers/LoginPopup";
// import {SelectionPopup} from "shared/containers/SelectionPopup";
import {SelectionPopup} from "shared/containers/SelectionPopup";
// import {SideNav} from "shared/containers/SideNav";

// import {PropertyModule} from "shared/containers/SelectionPopup/modules/PropertyModule";

// import {useDigitalCircuit} from "site/digital/utils/hooks/useDigitalCircuit";
import {useMainDigitalDesigner} from "site/digital/utils/hooks/useDigitalDesigner";

import {DigitalHeader} from "site/digital/containers/DigitalHeader";
import {DigitalItemNav} from "site/digital/containers/DigitalItemNav";
Expand All @@ -25,7 +25,7 @@ import {DigitalMainDesigner} from "site/digital/containers/DigitalMainDesigne
// import {ReplaceComponentDropdownModule} from "site/digital/containers/SelectionPopup/modules/ReplaceComponentDropdownModule";
// import {ViewICButtonModule} from "site/digital/containers/SelectionPopup/modules/ViewICButtonModule";

// import docsConfig from "site/digital/data/docsUrlConfig.json";
import docsConfig from "site/digital/data/docsUrlConfig.json";
// import exampleConfig from "site/digital/data/examples.json";

import "./index.scss";
Expand All @@ -42,7 +42,7 @@ import "./index.scss";
// } as CircuitMetadata));

export const App = () => {
// const circuit = useDigitalCircuit("main");
const designer = useMainDigitalDesigner();
const { h } = useWindowSize();

return (
Expand All @@ -58,16 +58,17 @@ export const App = () => {
<DigitalItemNav />
{/* <HistoryBox circuit={circuit} /> */}

{/* <SelectionPopup circuit={circuit} docsUrlConfig={docsConfig}>
<PropertyModule circuit={circuit} />
<SelectionPopup designer={designer} docsUrlConfig={docsConfig}>
{null}
{/* <PropertyModule circuit={circuit} />
<PortCountModule circuit={circuit} />
<OscilloscopeModule circuit={circuit} />
<ClockSyncButtonModule circuit={circuit} />
<BusButtonModule circuit={circuit} />
<ReplaceComponentDropdownModule circuit={circuit} />
<CreateICButtonModule circuit={circuit} />
<ViewICButtonModule circuit={circuit} />
</SelectionPopup> */}
<ViewICButtonModule circuit={circuit} /> */}
</SelectionPopup>

{/* <ContextMenu circuit={circuit} /> */}
</main>
Expand Down
2 changes: 1 addition & 1 deletion src/site/shared/circuitdesigner/CircuitDesigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface CircuitDesigner<CircuitT extends Circuit = Circuit> {
curPressedObj?: Obj;

// A margin relative to the current viewport (Camera) used
// for calculating the current "usable" viewport specifically
// for calculating the current "usable" viewport specifically
// for fitting the camera. I.e. when the ItemNav is open, this margin
// cuts off part of the canvas that is actually usable. (Issue #656)
// TODO[master](leon) - See if maybe we can replace this with tracking if the ItemNav is open
Expand Down