Files
graphfx/ui/Components/Graph.vue
2019-01-22 12:52:22 +01:00

187 lines
5.6 KiB
Vue

<template>
<div class="graph">
<canvas
v-if="boundingRect"
class="graph__bg"
ref="bg"
:width="boundingRect.width"
:height="boundingRect.height"
@click="showContextMenu"
></canvas>
<Draggable
v-for="(node, index) in graph"
:key="index"
@move="updateNodePosition(node, $event)"
>
<Node
class="graph__node"
:style="{transform: `translate(${node.x}px, ${node.y}px)`}"
:node="node.node"
ref="node"
:selectedInput="selectedInput"
:selectedOutput="selectedOutput"
@outputSelected="selectedOutput = $event"
@inputSelected="selectedInput = $event"
@removeNode="removeNode"
/>
</Draggable>
<ContextMenu
:open="!!contextMenuPosition"
:position="contextMenuPosition"
:items="availableNodes"
@selected="addNode"
/>
</div>
</template>
<script>
import * as nodes from '../../src/nodes';
import Node from './Node.vue';
import Draggable from './Drag/Draggable.js';
import ContextMenu from './ContextMenu.vue';
export default {
components: {
ContextMenu,
Draggable,
Node,
},
props: {
graph: {
type: Array,
required: true,
}
},
data() {
return {
selectedOutput: null,
selectedInput: null,
boundingRect: null,
contextMenuPosition: null,
}
},
watch: {
selectedOutput() {
this.tryConnectIO();
},
selectedInput() {
this.tryConnectIO();
},
graph: {
deep: true,
handler() {
this.drawConnections();
}
}
},
methods: {
showContextMenu(event) {
const {offsetX, offsetY} = event;
if (this.contextMenuPosition) {
this.contextMenuPosition = null;
} else {
this.contextMenuPosition = {
x: offsetX,
y: offsetY,
};
}
},
updateNodePosition(node, {clientX, clientY, x0, y0}) {
const x = clientX - this.$el.offsetLeft;
const y = clientY - this.$el.offsetTop;
console.log('updateNodePosition', x, y)
node.x = x;
node.y = y;
},
tryConnectIO() {
if (this.selectedInput && this.selectedOutput) {
this.selectedInput.connect(this.selectedOutput);
this.selectedInput = null;
this.selectedOutput = null;
}
},
recomputeSize() {
this.boundingRect = this.$el.getBoundingClientRect();
},
drawConnections() {
try {
const flattenObjectArray = (ary) =>
ary.reduce((acc, nxt) => Object.assign(acc, nxt), {});
const inputs = flattenObjectArray(this.$refs.node
.map((vm) => vm.inputPositions()));
const outputs = flattenObjectArray(this.$refs.node
.map((vm) => vm.outputPositions()));
const bg = this.$refs.bg;
const ctx = bg.getContext('2d');
ctx.clearRect(0, 0, bg.width, bg.height);
ctx.strokeStyle = "#eaa11a";
ctx.lineWidth = 3;
for (let input of Object.values(inputs)) {
if (!input.output) continue;
const output = outputs[input.output.id];
ctx.beginPath();
ctx.moveTo(input.rect.left, input.rect.top);
const halfWay = {x: (input.rect.left + output.rect.right)/2, y: (input.rect.y + output.rect.y)/2};
ctx.bezierCurveTo(
Math.min(halfWay.x, input.rect.left - Math.max(Math.abs(halfWay.x - input.rect.left), 50)), input.rect.top,
Math.max(halfWay.x, output.rect.right + Math.max(Math.abs(halfWay.x - output.rect.right), 50)), output.rect.top,
// Math.min(halfWay.x, output.rect.right - (halfWay.x - output.rect.right)), output.rect.top,
//halfWay.x, output.rect.top,
output.rect.right, output.rect.top
);
ctx.stroke();
}
} catch (err) {
console.warn('drawConnection', err);
}
},
addNode({x, y, item: {node}}) {
this.graph.push({
x, y,
node: new node(),
});
this.contextMenuPosition = null;
},
removeNode(nodeToRemove) {
const index = this.graph.findIndex(({node}) => node === nodeToRemove);
console.log('nodeIndex', index);
this.graph.splice(index, 1);
}
},
computed: {
availableNodes() {
return Object.keys(nodes)
.map((name) => ({
label: name,
node: nodes[name],
}))
}
},
mounted() {
this.$nextTick(() => {
this.recomputeSize();
this.drawConnections();
})
}
}
</script>
<style>
.graph {
position: relative;
width: 100vw;
height: 100vh;
background: linear-gradient(#333333, #151515);
}
.graph__node {
position: absolute;
}
.graph__bg {
position: absolute;
top: 0;
left: 0;
}
</style>