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

Click Link to Select, CTRL+Click to add a Point #319

Merged
merged 5 commits into from
Apr 23, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/XircuitsApp.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as SRD from '@projectstorm/react-diagrams';
import { CustomNodeFactory } from "./node/CustomNodeFactory";
import { CustomNodeModel } from './node/CustomNodeModel';
import { ZoomCanvasAction } from '@projectstorm/react-canvas-core';
import { CustomActionEvent } from '../commands/CustomActionEvent';
import { ILabShell, JupyterFrontEnd } from '@jupyterlab/application';
import { CustomDiagramState } from './state/CustomDiagramState'
Expand All @@ -10,6 +9,7 @@ import { ParameterLinkFactory, TriangleLinkFactory } from './link/CustomLinkFact
import { PointModel } from '@projectstorm/react-diagrams';
import { Point } from '@projectstorm/geometry';
import { BaseComponentLibrary } from '../tray_library/BaseComponentLib';
import { CustomPanAndZoomCanvasAction } from "./actions/CustomPanAndZoomCanvasAction";

export class XircuitsApplication {

Expand All @@ -24,7 +24,7 @@ export class XircuitsApplication {
this.diagramEngine.getNodeFactories().registerFactory(new CustomNodeFactory(app, shell));
this.diagramEngine.getLinkFactories().registerFactory(new ParameterLinkFactory());
this.diagramEngine.getLinkFactories().registerFactory(new TriangleLinkFactory());
this.diagramEngine.getActionEventBus().registerAction(new ZoomCanvasAction({ inverseZoom: true }))
this.diagramEngine.getActionEventBus().registerAction(new CustomPanAndZoomCanvasAction())
this.diagramEngine.getActionEventBus().registerAction(new CustomActionEvent({ app }));
this.diagramEngine.getStateMachine().pushState(new CustomDiagramState());

Expand Down
8 changes: 8 additions & 0 deletions src/components/XircuitsBodyWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,14 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
hidePanel();
};

useEffect(() => {
const canvas = xircuitsApp.getDiagramEngine().getCanvas()
canvas.addEventListener('wheel', preventDefault);
return () => {
canvas.removeEventListener('wheel', preventDefault);
}
}, [xircuitsApp.getDiagramEngine().getCanvas()])

return (
<Body>
<Content>
Expand Down
74 changes: 74 additions & 0 deletions src/components/actions/CustomPanAndZoomCanvasAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { WheelEvent } from 'react';
import { Action, ActionEvent, InputType } from '@projectstorm/react-canvas-core';

export interface CustomPanAndZoomCanvasActionOptions {
inverseZoom?: boolean;
}

export class CustomPanAndZoomCanvasAction extends Action {
constructor(options: CustomPanAndZoomCanvasActionOptions = {}) {
super({
type: InputType.MOUSE_WHEEL,
fire: (actionEvent: ActionEvent<WheelEvent>) => {
const { event } = actionEvent;
// we can block layer rendering because we are only targeting the transforms
for (let layer of this.engine.getModel().getLayers()) {
layer.allowRepaint(false);
}

const model = this.engine.getModel();
event.stopPropagation();
if (event.ctrlKey) {
// Pinch and zoom gesture
const oldZoomFactor = this.engine.getModel().getZoomLevel() / 100;

let scrollDelta = options.inverseZoom ? event.deltaY : -event.deltaY;
//check if it is pinch gesture
if (event.ctrlKey && scrollDelta % 1 !== 0) {
/*
Chrome and Firefox sends wheel event with deltaY that
have fractional part, also `ctrlKey` prop of the event is true
though ctrl isn't pressed
*/
scrollDelta /= 3;
} else {
scrollDelta /= 60;
}

if (model.getZoomLevel() + scrollDelta > 10) {
model.setZoomLevel(model.getZoomLevel() + scrollDelta);
}

const zoomFactor = model.getZoomLevel() / 100;

const boundingRect = event.currentTarget.getBoundingClientRect();
const clientWidth = boundingRect.width;
const clientHeight = boundingRect.height;
// compute difference between rect before and after scroll
const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor;
const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor;
// compute mouse coords relative to canvas
const clientX = event.clientX - boundingRect.left;
const clientY = event.clientY - boundingRect.top;

// compute width and height increment factor
const xFactor = (clientX - model.getOffsetX()) / oldZoomFactor / clientWidth;
const yFactor = (clientY - model.getOffsetY()) / oldZoomFactor / clientHeight;

model.setOffset(model.getOffsetX() - widthDiff * xFactor, model.getOffsetY() - heightDiff * yFactor);
} else {
// Pan gesture
let yDelta = options.inverseZoom ? -event.deltaY : event.deltaY;
let xDelta = options.inverseZoom ? -event.deltaX : event.deltaX;
model.setOffset(model.getOffsetX() - xDelta, model.getOffsetY() - yDelta);
}
this.engine.repaintCanvas();

// re-enable rendering
for (let layer of this.engine.getModel().getLayers()) {
layer.allowRepaint(true);
}
}
});
}
}
96 changes: 93 additions & 3 deletions src/components/link/CustomLinkFactory.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DefaultLinkFactory } from '@projectstorm/react-diagrams';
import { DefaultLinkFactory, DefaultLinkWidget } from "@projectstorm/react-diagrams";
import { LinkWidget } from '@projectstorm/react-diagrams-core';
import * as React from 'react';
import { ParameterLinkModel, TriangleLinkModel } from './CustomLinkModel';
import styled from '@emotion/styled';
Expand Down Expand Up @@ -46,7 +47,96 @@ function removeHover(model: TriangleLinkModel | ParameterLinkModel){
}
}

export class ParameterLinkFactory extends DefaultLinkFactory {
class SelectOnClickLinkWidget extends DefaultLinkWidget {
constructor(type) {
super(type);
}
addPointToLink(event: React.MouseEvent, index: number) {
if (
event.ctrlKey &&
!this.props.link.isLocked() &&
this.props.link.getPoints().length - 1 <= this.props.diagramEngine.getMaxNumberPointsPerLink()
) {
event.stopPropagation();

const position = this.props.diagramEngine.getRelativeMousePoint(event);
const point = this.props.link.point(position.x, position.y, index);
event.persist();
this.forceUpdate(() => {
this.props.diagramEngine.getActionEventBus().fireAction({
event,
model: point
});
});
}
}

render() {
//ensure id is present for all points on the path
var points = this.props.link.getPoints();
var paths = [];
this.refPaths = [];

if (points.length === 2) {
paths.push(
this.generateLink(
this.props.link.getSVGPath(),
{
onMouseDown: (event) => {
this.props.selected?.(event);
this.addPointToLink(event, 1);
}
},
'0'
)
);

// draw the link as dangeling
if (this.props.link.getTargetPort() == null) {
paths.push(this.generatePoint(points[1]));
}
} else {
//draw the multiple anchors and complex line instead
for (let j = 0; j < points.length - 1; j++) {
paths.push(
this.generateLink(
LinkWidget.generateLinePath(points[j], points[j + 1]),
{
'data-linkid': this.props.link.getID(),
'data-point': j,
onMouseDown: (event) => {
this.props.selected?.(event);
this.addPointToLink(event, j + 1);
}
},
j
)
);
}

if (this.renderPoints()) {
//render the circles
for (let i = 1; i < points.length - 1; i++) {
paths.push(this.generatePoint(points[i]));
}

if (this.props.link.getTargetPort() == null) {
paths.push(this.generatePoint(points[points.length - 1]));
}
}
}

return <g data-default-link-test={this.props.link.getOptions().testName}>{paths}</g>;
}
}

class SelectOnClickLinkFactory extends DefaultLinkFactory {
generateReactWidget(event: any): JSX.Element {
return <SelectOnClickLinkWidget link={event.model} diagramEngine={this.engine} />;
}
}

export class ParameterLinkFactory extends SelectOnClickLinkFactory {
constructor() {
super('parameter-link');
}
Expand All @@ -69,7 +159,7 @@ export class ParameterLinkFactory extends DefaultLinkFactory {
}
}

export class TriangleLinkFactory extends DefaultLinkFactory {
export class TriangleLinkFactory extends SelectOnClickLinkFactory {
constructor() {
super('triangle-link');
}
Expand Down