188 lines
4.3 KiB
JavaScript
188 lines
4.3 KiB
JavaScript
|
const width = 600;
|
||
|
const height = 300;
|
||
|
const radius = 25;
|
||
|
|
||
|
const settings = {
|
||
|
physics: true,
|
||
|
speed: 2,
|
||
|
snapToPadding: 10
|
||
|
};
|
||
|
|
||
|
const canvas = document.getElementById('canvas');
|
||
|
const ctx = canvas.getContext('2d');
|
||
|
|
||
|
canvas.width = width;
|
||
|
canvas.height = height;
|
||
|
|
||
|
const states = [];
|
||
|
const connections = [];
|
||
|
|
||
|
function tick() {
|
||
|
states.forEach(stateA => {
|
||
|
states.forEach(stateB => {
|
||
|
if (stateA !== stateB && stateA.intersects(stateB)) {
|
||
|
const inter = stateA.intersection(stateB);
|
||
|
const angle1 = stateA.directionTo(stateB);
|
||
|
const angle2 = angle1 + Math.PI;
|
||
|
|
||
|
const x1 = Math.cos(angle1) * inter + stateA.x;
|
||
|
const y1 = Math.sin(angle1) * inter + stateA.y;
|
||
|
const x2 = Math.cos(angle2) * inter + stateB.x;
|
||
|
const y2 = Math.sin(angle2) * inter + stateB.y;
|
||
|
|
||
|
stateA.moveTo(x1, y1);
|
||
|
stateB.moveTo(x2, y2);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
states.forEach(state => {
|
||
|
if (state.v) {
|
||
|
state.moveToStep();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
setTimeout(tick, 1000 / 30);
|
||
|
}
|
||
|
|
||
|
function draw() {
|
||
|
ctx.clearRect(0, 0, width, height);
|
||
|
|
||
|
connections.forEach(connection => connection.draw());
|
||
|
|
||
|
states.forEach(state => state.draw());
|
||
|
|
||
|
setTimeout(draw, 1000 / 60);
|
||
|
}
|
||
|
|
||
|
function selectObject(x, y) {
|
||
|
for (let state of states) {
|
||
|
if ((x - state.x) * (x - state.x) + (y - state.y) * (y - state.y) < radius * radius) {
|
||
|
return state;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getStateByPos(x, y) {
|
||
|
}
|
||
|
|
||
|
function checkForState(x, y) {
|
||
|
return !!getStateByPos(x, y);
|
||
|
}
|
||
|
|
||
|
function addState(x, y) {
|
||
|
const state = new State(x, y, '#f0f');
|
||
|
states.push(state);
|
||
|
}
|
||
|
|
||
|
function addConnection(stateA, stateB, angleA, angleB) {
|
||
|
const connection = new Connection(stateA, stateB, angleA, angleB);
|
||
|
connections.push(connection);
|
||
|
}
|
||
|
|
||
|
let isMouseDown = false,
|
||
|
isDragging = false,
|
||
|
lastDrag = 0,
|
||
|
selectedObject = null,
|
||
|
shiftPressed = false,
|
||
|
currentConnection = null,
|
||
|
movingObject = false;
|
||
|
|
||
|
canvas.addEventListener('mousedown', (event) => {
|
||
|
selectedObject = selectObject(event.x, event.y);
|
||
|
|
||
|
shiftPressed = event.shiftKey;
|
||
|
|
||
|
if (!!selectedObject) {
|
||
|
if (shiftPressed && selectedObject instanceof State) {
|
||
|
currentConnection = new SelfConnection(selectedObject, event);
|
||
|
} else {
|
||
|
movingObject = true;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
canvas.addEventListener('mousemove', (event) => {
|
||
|
if (isMouseDown && !isDragging)
|
||
|
isDragging = true;
|
||
|
|
||
|
if (isMouseDown && isDragging) {
|
||
|
if (!shiftPressed) {
|
||
|
selectedObject.jumpTo(event.clientX, event.clientY);
|
||
|
}
|
||
|
|
||
|
selectedObject.setAnchorPoint(event.clientX, event.clientY);
|
||
|
}
|
||
|
|
||
|
if (!!currentConnection) {
|
||
|
let targetState = selectObject(event.x, event.y);
|
||
|
|
||
|
if (!targetState instanceof State) {
|
||
|
targetState = null;
|
||
|
}
|
||
|
|
||
|
if (!!selectedObject) {
|
||
|
if (targetState === selectedObject) {
|
||
|
currentConnection = new SelfConnection(selectedObject, event);
|
||
|
} else if(!!targetState) {
|
||
|
currentConnection = new Connection(selectedObject, targetState);
|
||
|
} else {
|
||
|
// currentConnection = new
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
canvas.addEventListener('mouseup', (event) => {
|
||
|
if (isDragging) {
|
||
|
lastDrag = Date.now()
|
||
|
}
|
||
|
|
||
|
if (shiftPressed) {
|
||
|
const state = getStateByPos(event.clientX, event.clientY);
|
||
|
addConnection(selectedObject, state, Math.PI, -Math.PI);
|
||
|
}
|
||
|
|
||
|
isMouseDown = false;
|
||
|
isDragging = false;
|
||
|
selectedObject = null;
|
||
|
});
|
||
|
|
||
|
canvas.addEventListener('click', (event) => {
|
||
|
const x = event.clientX,
|
||
|
y = event.clientY;
|
||
|
|
||
|
if (Date.now() - lastDrag < 100) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (checkForState(x, y)) {
|
||
|
}
|
||
|
});
|
||
|
|
||
|
canvas.addEventListener('dblclick', (event) => {
|
||
|
const x = event.clientX,
|
||
|
y = event.clientY;
|
||
|
|
||
|
addState(x, y);
|
||
|
});
|
||
|
|
||
|
CanvasRenderingContext2D.prototype.drawArrow = function (x, y, angle) {
|
||
|
const dx = Math.cos(angle);
|
||
|
const dy = Math.sin(angle);
|
||
|
|
||
|
this.beginPath();
|
||
|
|
||
|
this.moveTo(x, y);
|
||
|
this.lineTo(x - 8 * dx + 5 * dy, y - 8 * dy - 5 * dx);
|
||
|
this.lineTo(x - 8 * dx - 5 * dy, y - 8 * dy + 5 * dx);
|
||
|
|
||
|
this.fill();
|
||
|
};
|
||
|
|
||
|
tick();
|
||
|
draw();
|