feat(editor): dock flow arrows square at any pipe angle

Swap the filled-triangle arrowhead for an open arrow on flow pipes and
information links, and route flow pipes through a custom cubic edge whose
horizontal flatten arm scales with the vertical drop, not just the
horizontal gap. Vue Flow's default arm collapses toward zero when the
valve sits nearly above the stock, so a steep pipe stayed flat only in
its last pixels and the arrowhead detached from the visible line. The
pipe now keeps a real horizontal run into the stock's left edge at any
approach angle.
This commit is contained in:
Julien Calixte
2026-06-20 11:51:34 +02:00
parent df03012c8b
commit 21a459256a
3 changed files with 43 additions and 2 deletions

View File

@@ -36,6 +36,7 @@ import GlossPanel from "./GlossPanel.vue"
import LoopOverlay from "./LoopOverlay.vue"
import Palette from "./Palette.vue"
import InfoLinkEdge from "./edges/InfoLinkEdge.vue"
import PipeEdge from "./edges/PipeEdge.vue"
import CloudNode from "./nodes/CloudNode.vue"
import ConverterNode from "./nodes/ConverterNode.vue"
import FlowNode from "./nodes/FlowNode.vue"
@@ -405,6 +406,9 @@ onBeforeUnmount(() => {
<CloudNode v-bind="nodeProps" />
</template>
<template #edge-pipe="edgeProps">
<PipeEdge v-bind="edgeProps" />
</template>
<template #edge-info="edgeProps">
<InfoLinkEdge v-bind="edgeProps" />
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
/**
* Flow pipe edge (C3) — the source→valve and valve→target segments of a Flow.
*
* The pipe leaves the source on the right and docks square into the target's
* left edge (horizontal tangents both ends), so the arrowhead always enters the
* stock straight-on. Vue Flow's default bezier does the same — but it sizes the
* horizontal "flatten" arm from the *horizontal* gap alone, so when the valve
* sits nearly above the stock that arm collapses to ~zero: the curve stays steep
* and snaps flat only in its final pixels, leaving the arrowhead on a stub of
* horizontal line while the visible stroke arrives steep — the head detaches.
*
* Sizing the arm from the vertical drop too keeps a real horizontal run before
* the stock at any approach angle, so the arrowhead sits on the line and still
* docks square. BaseEdge draws the stroke (same .vue-flow__edge-path class, so
* theme stroke/width are unchanged) and carries the markerEnd through.
*/
import { BaseEdge, type EdgeProps } from "@vue-flow/core"
import { computed } from "vue"
import type { EdgeData } from "@/model/projection"
const props = defineProps<EdgeProps<EdgeData>>()
const path = computed(() => {
const { sourceX: sx, sourceY: sy, targetX: tx, targetY: ty } = props
const arm = Math.min(120, Math.max(30, Math.abs(tx - sx) * 0.5, Math.abs(ty - sy) * 0.45))
// Cubic with horizontal control points: out the source's right, into the
// target's left. `arm` is the run over which each end stays horizontal.
return `M ${sx},${sy} C ${sx + arm},${sy} ${tx - arm},${ty} ${tx},${ty}`
})
</script>
<template>
<BaseEdge :id="props.id" :path="path" :marker-end="props.markerEnd" :style="props.style" />
</template>