Skip to content

Commit

Permalink
feat: in/out animations for nodes and socket fields
Browse files Browse the repository at this point in the history
  • Loading branch information
wooningeire committed May 11, 2024
1 parent 2d3a367 commit ae80a67
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 137 deletions.
147 changes: 100 additions & 47 deletions src/components/TheNodeTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,23 @@ const onWheel = (event: WheelEvent) => {
};
let animationHandle = 0;
const onAnimationStart = (event: AnimationEvent) => {
if (!(event.target! as HTMLElement).classList.contains("socket-value-editor")) return;
updateLinksEveryFrame();
};
const onAnimationEnd = (event: AnimationEvent) => {
if (!(event.target! as HTMLElement).classList.contains("socket-value-editor")) return;
cancelAnimationFrame(animationHandle);
};
const updateLinksEveryFrame = () => {
rerenderLinks();
animationHandle = requestAnimationFrame(updateLinksEveryFrame);
};
defineExpose({
selectNode,
reloadOutputs,
Expand All @@ -184,57 +201,71 @@ defineExpose({

<template>
<!-- drag events here are from node tray -->
<div class="node-tree"
@dragover="event => isDraggingNodeFromNodeTray && event.preventDefault()"
@drop="event => isDraggingNodeFromNodeTray && $emit('add-node', currentlyDraggedNodeConstructor, [event.pageX, event.pageY])"

@pointerdown.self="onPointerDownSelf"
@pointerdown="event => event.button === 1 && beginDragCamera(event)"
@wheel.passive="onWheel"

:style="{
'--pos-x': `${viewportPos[0]}px`,
'--pos-y': `${viewportPos[1]}px`,
'--scale': `${viewportScale}`,
} as any">
<div class="nodes">
<NodeVue
v-for="node of tree.nodes"
:key="node.id"
:node="(node as Node)"
@drag-socket="onDragSocket"
@link-to-socket="onLinkToSocket"
@node-selected="selectNode"

@potential-socket-position-change="rerenderLinks"

@field-value-change="(requiresShaderReload, updateSource) => reloadOutputs(requiresShaderReload, updateSource)"
@tree-update="reloadOutputs(true, NodeUpdateSource.TreeReload)"
/>
<div
class="node-tree"
@dragover="event => isDraggingNodeFromNodeTray && event.preventDefault()"
@drop="event => isDraggingNodeFromNodeTray && $emit('add-node', currentlyDraggedNodeConstructor, [event.pageX, event.pageY])"

@pointerdown.self="onPointerDownSelf"
@pointerdown="event => event.button === 1 && beginDragCamera(event)"
@wheel.passive="onWheel"

:style="{
'--pos-x': `${viewportPos[0]}px`,
'--pos-y': `${viewportPos[1]}px`,
'--scale': `${viewportScale}`,
} as any"
>
<div
class="nodes"
@animationstart.capture="onAnimationStart"
@animationend.capture="onAnimationEnd"
>
<TransitionGroup name="pop-in">
<NodeVue
v-for="node of tree.nodes"
:key="node.id"
:node="(node as Node)"
@drag-socket="onDragSocket"
@link-to-socket="onLinkToSocket"
@node-selected="selectNode"

@potential-socket-position-change="rerenderLinks"

@field-value-change="(requiresShaderReload, updateSource) => reloadOutputs(requiresShaderReload, updateSource)"
@tree-update="reloadOutputs(true, NodeUpdateSource.TreeReload)"
/>
</TransitionGroup>
</div>

<svg class="links"
:viewbox="`0 0 ${$el?.clientWidth ?? 300} ${$el?.clientHeight ?? 150}`">
<svg
class="links"
:viewbox="`0 0 ${$el?.clientWidth ?? 300} ${$el?.clientHeight ?? 150}`"
>
<g>
<template v-if="draggingSocket">
<NodeLink v-if="draggedSocket?.isOutput"
class="new-link"
:link="null"
:socketVues="socketVues"

:x0="draggedSocketVue?.socketPos()[0] ?? 0"
:y0="draggedSocketVue?.socketPos()[1] ?? 0"
:x1="pointerX"
:y1="pointerY" />
<NodeLink v-else
class="new-link"
:link="null"
:socketVues="socketVues"

:x0="pointerX"
:y0="pointerY"
:x1="draggedSocketVue?.socketPos()[0] ?? 0"
:y1="draggedSocketVue?.socketPos()[1] ?? 0" />
<NodeLink
v-if="draggedSocket?.isOutput"
class="new-link"
:link="null"
:socketVues="socketVues"

:x0="draggedSocketVue?.socketPos()[0] ?? 0"
:y0="draggedSocketVue?.socketPos()[1] ?? 0"
:x1="pointerX"
:y1="pointerY"
/>
<NodeLink
v-else
class="new-link"
:link="null"
:socketVues="socketVues"

:x0="pointerX"
:y0="pointerY"
:x1="draggedSocketVue?.socketPos()[0] ?? 0"
:y1="draggedSocketVue?.socketPos()[1] ?? 0"
/>
</template>

<TheNodeTreeLinks :socketVues="socketVues" />
Expand Down Expand Up @@ -269,6 +300,28 @@ defineExpose({
> :deep(*) {
pointer-events: initial;
}
// vue transition
> .pop-in-enter-active {
animation: pop-in .25s cubic-bezier(.26,1.5,.39,.99);
}
> .pop-in-leave-active {
pointer-events: none;
animation: fade-out .125s cubic-bezier(.44,0,1,.71);
}
@keyframes pop-in {
0% {
opacity: 0;
transform: scale(0);
}
}
@keyframes fade-out {
100% {
transform: scale(0.5);
opacity: 0;
}
}
}
> .nodes,
Expand Down
Empty file.
47 changes: 30 additions & 17 deletions src/components/node/NodeLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,49 @@ C${x0! + controlPointDx},${y0} ${x1! - controlPointDx},${y1}, ${x1},${y1}`;
</script>

<template>
<path v-if="socketLoaded"
:d="path"

:class="{
subtle,
invalid,
}" />
<path
v-if="socketLoaded"
:d="path"

:class="{
subtle,
invalid,
}"
/>
</template>

<style lang="scss" scoped>
path {
--dasharray-length: -22px;
fill: none;
transition:
stroke .25s ease-in-out,
stroke-dasharray .5s ease-in-out;
animation: move-stroke 0.5s infinite linear;
stroke-dasharray: 20px 2px;
@keyframes move-stroke {
0% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: var(--dasharray-length);
}
}
&.subtle {
opacity: 0.25;
}
&.invalid {
--dasharray-length: -8px;
stroke: #f68;
stroke-dasharray: 4px;
animation: move-stroke 0.25s infinite linear;
@keyframes move-stroke {
0% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: -8px;
}
}
}
}
</style>
108 changes: 77 additions & 31 deletions src/components/node/NodeSocket.vue
Original file line number Diff line number Diff line change
Expand Up @@ -163,52 +163,76 @@ Object.defineProperties(socketVue, {
},
});
// for transition
const height = ref(0);
const nodeSocketFieldRef = ref<InstanceType<typeof NodeSocketField> | null>(null);
onMounted(() => {
if (!shouldShowFields.value) return;
height.value = nodeSocketFieldRef.value!.$el.clientHeight;
});
</script>

<template>
<div class="socket-container"
:class="{'in': socket.isInput}"
ref="socketContainer"
<div
class="socket-container"
:class="{'in': socket.isInput}"
ref="socketContainer"

@pointerover="() => !draggedSocket && showTooltip()"
@pointerout="tooltipController.hideTooltip()"
>
<div
class="socket"
v-if="socket.showSocket"

ref="socketHitbox"
draggable="true"
@dragstart="event => (ondragstart(event), tooltipController.hideTooltip())"
@dragenter.prevent
@dragover.prevent="isDraggedOver = true"
@drop="event => (ondrop(event), isDraggedOver = false)"
@pointerdown="event => event.button === 0 && event.stopPropagation()"
@dragleave="isDraggedOver = false"
@dragend="event => (event.currentTarget as HTMLDivElement)?.blur()"

@dblclick="unlinkLinks"

@pointerover="() => !draggedSocket && showTooltip()"
@pointerout="tooltipController.hideTooltip()">
<div class="socket"
v-if="socket.showSocket"

ref="socketHitbox"
draggable="true"
@dragstart="event => (ondragstart(event), tooltipController.hideTooltip())"
@dragenter.prevent
@dragover.prevent="isDraggedOver = true"
@drop="event => (ondrop(event), isDraggedOver = false)"
@pointerdown="event => event.button === 0 && event.stopPropagation()"
@dragleave="isDraggedOver = false"
@dragend="event => (event.currentTarget as HTMLDivElement)?.blur()"

@dblclick="unlinkLinks"

:class="{
hiding: Boolean(draggedSocket) && !canLinkDraggedSocket,
}"
>
<div
class="socket-display"
:class="{
hiding: Boolean(draggedSocket) && !canLinkDraggedSocket,
}">
<div class="socket-display"
:class="{
constant: socket.constant,
excited: Boolean(draggedSocket) && canLinkDraggedSocket,
accepting: isDraggedOver && canLinkDraggedSocket,
}"
:style="{'--socket-color': socketColor} as any"></div>
constant: socket.constant,
excited: Boolean(draggedSocket) && canLinkDraggedSocket,
accepting: isDraggedOver && canLinkDraggedSocket,
}"
:style="{'--socket-color': socketColor} as any"
></div>
</div>
<div
class="socket-label"
v-html="getString(socket.label)"
></div>

<NodeSocketField v-if="shouldShowFields"
<Transition
name="drawer"
:style="{
'--end-height': `-${height}px`,
}"
ref=""
>
<NodeSocketField
v-if="shouldShowFields"
:socket="socket"
@value-change="(requiresShaderReload) => {
socket.node.onSocketFieldValueChange(socket, tree as Tree);
$emit('field-value-change', requiresShaderReload, socket);
}" />
}"
ref="nodeSocketFieldRef"
/>
</Transition>
</div>
</template>

Expand Down Expand Up @@ -299,5 +323,27 @@ Object.defineProperties(socketVue, {
right: var(--socket-offset);
}
}
// vue transition
> .drawer-enter-active {
animation: drawer .125s cubic-bezier(0,.74,.55,1);
}
> .drawer-leave-active {
pointer-events: none;
animation: drawer .125s cubic-bezier(.45,0,1,.26) reverse;
}
@keyframes drawer {
0% {
transform: scaleY(0);
margin-bottom: var(--end-height);
}
100% {
margin-bottom: 0;
}
}
}
</style>
Loading

0 comments on commit ae80a67

Please sign in to comment.