mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-05-13 23:37:30 +08:00
Simplify arc calculation
This commit is contained in:
@@ -84,6 +84,104 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
|
||||
this.element.destination = inputPin
|
||||
}
|
||||
|
||||
/** @param {PropertyValues} changedProperties */
|
||||
#calculateSVGPath(changedProperties) {
|
||||
const originPin = this.element.source
|
||||
const targetPin = this.element.destination
|
||||
const isOriginAKnot = originPin?.isKnot()
|
||||
const isTargetAKnot = targetPin?.isKnot()
|
||||
const from = this.element.fromX
|
||||
const to = this.element.toX
|
||||
|
||||
// Switch actual input/output pins if allowed and makes sense
|
||||
if (isOriginAKnot && (!targetPin || isTargetAKnot)) {
|
||||
if (originPin?.isInputLoosely() && to > from + Configuration.distanceThreshold) {
|
||||
this.element.source = /** @type {KnotPinTemplate} */(originPin.template).oppositePin()
|
||||
} else if (originPin?.isOutputLoosely() && to < from - Configuration.distanceThreshold) {
|
||||
this.element.source = /** @type {KnotPinTemplate} */(originPin.template).oppositePin()
|
||||
}
|
||||
}
|
||||
if (isTargetAKnot && (!originPin || isOriginAKnot)) {
|
||||
if (targetPin?.isInputLoosely() && to < from - Configuration.distanceThreshold) {
|
||||
this.element.destination = /** @type {KnotPinTemplate} */(targetPin.template).oppositePin()
|
||||
} else if (targetPin?.isOutputLoosely() && to > from + Configuration.distanceThreshold) {
|
||||
this.element.destination = /** @type {KnotPinTemplate} */(targetPin.template).oppositePin()
|
||||
}
|
||||
}
|
||||
|
||||
// Switch visual input/output pins if allowed and makes sense
|
||||
let directionsCheckedKnot
|
||||
if (
|
||||
originPin?.isKnot()
|
||||
&& !changedProperties.has("fromX")
|
||||
&& changedProperties.has("toX")
|
||||
) {
|
||||
// The target end has moved and origin end is a knot
|
||||
directionsCheckedKnot = originPin.nodeElement
|
||||
} else if (
|
||||
targetPin?.isKnot()
|
||||
&& changedProperties.has("toX")
|
||||
&& !changedProperties.has("fromX")
|
||||
) {
|
||||
// The source end has moved and target end is a knot
|
||||
directionsCheckedKnot = targetPin.nodeElement
|
||||
}
|
||||
if (directionsCheckedKnot) {
|
||||
let leftPinsLocation = 0
|
||||
let leftPinsCount = 0
|
||||
let rightPinsLocation = 0
|
||||
let rightPinsCount = 0
|
||||
const pins = directionsCheckedKnot.template
|
||||
.getAllConnectedLinks()
|
||||
.map(l => l.getOtherPin(directionsCheckedKnot))
|
||||
for (const pin of pins) {
|
||||
if (pin.isInput()) {
|
||||
rightPinsLocation += pin.getLinkLocation()[0]
|
||||
++rightPinsCount
|
||||
} else if (pin.isOutput()) {
|
||||
leftPinsLocation += pin.getLinkLocation()[0]
|
||||
++leftPinsCount
|
||||
}
|
||||
}
|
||||
leftPinsLocation /= leftPinsCount
|
||||
rightPinsLocation /= rightPinsCount
|
||||
const knotTemplate = /** @type {KnotNodeTemplate} */(directionsCheckedKnot.template)
|
||||
if ((rightPinsLocation < leftPinsLocation) != knotTemplate.switchDirectionsVisually) {
|
||||
knotTemplate.switchDirectionsVisually = rightPinsLocation < leftPinsLocation
|
||||
}
|
||||
}
|
||||
let sameDirection = originPin?.isInputVisually() == targetPin?.isInputVisually()
|
||||
|
||||
// Actual computation
|
||||
const dx = Math.max(Math.abs(this.element.fromX - this.element.toX), 1)
|
||||
const dy = Math.max(Math.abs(this.element.fromY - this.element.toY), 1)
|
||||
const width = Math.max(dx, Configuration.linkMinWidth)
|
||||
const fillRatio = dx / width
|
||||
const xInverted = this.element.originatesFromInput
|
||||
? this.element.fromX < this.element.toX
|
||||
: this.element.toX < this.element.fromX
|
||||
this.element.startPixels = dx < width // If under minimum width
|
||||
? (width - dx) / 2 // Start from half the empty space
|
||||
: 0 // Otherwise start from the beginning
|
||||
this.element.startPercentage = xInverted ? this.element.startPixels + fillRatio * 100 : this.element.startPixels
|
||||
const c1 =
|
||||
this.element.startPercentage
|
||||
+ (xInverted
|
||||
? LinkTemplate.c1DecreasingValue(width)
|
||||
: 10
|
||||
)
|
||||
* fillRatio
|
||||
const aspectRatio = dy / Math.max(30, dx)
|
||||
const c2 = sameDirection
|
||||
? (this.element.startPercentage + 50)
|
||||
: (
|
||||
LinkTemplate.c2Clamped(dx)
|
||||
* LinkTemplate.sigmoidPositive(fillRatio * 1.2 + aspectRatio * 0.5, 1.5, 1.8)
|
||||
+ this.element.startPercentage
|
||||
)
|
||||
this.element.svgPathD = Configuration.linkRightSVGPath(this.element.startPercentage, c1, c2, sameDirection)
|
||||
}
|
||||
|
||||
createInputObjects() {
|
||||
/** @type {HTMLElement} */
|
||||
const linkArea = this.element.querySelector(".ueb-link-area")
|
||||
@@ -117,68 +215,23 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
|
||||
/** @param {PropertyValues} changedProperties */
|
||||
willUpdate(changedProperties) {
|
||||
super.willUpdate(changedProperties)
|
||||
const sourcePin = this.element.source
|
||||
const destinationPin = this.element.destination
|
||||
if (changedProperties.has("fromX") || changedProperties.has("toX")) {
|
||||
const from = this.element.fromX
|
||||
const to = this.element.toX
|
||||
const isSourceAKnot = sourcePin?.isKnot()
|
||||
const isDestinationAKnot = destinationPin?.isKnot()
|
||||
if (isSourceAKnot && (!destinationPin || isDestinationAKnot)) {
|
||||
if (sourcePin?.isInputLoossly() && to > from + Configuration.distanceThreshold) {
|
||||
this.element.source = /** @type {KnotPinTemplate} */(sourcePin.template).oppositePin()
|
||||
} else if (sourcePin?.isOutputLoosely() && to < from - Configuration.distanceThreshold) {
|
||||
this.element.source = /** @type {KnotPinTemplate} */(sourcePin.template).oppositePin()
|
||||
}
|
||||
}
|
||||
if (isDestinationAKnot && (!sourcePin || isSourceAKnot)) {
|
||||
if (destinationPin?.isInputLoossly() && to < from - Configuration.distanceThreshold) {
|
||||
this.element.destination = /** @type {KnotPinTemplate} */(destinationPin.template).oppositePin()
|
||||
} else if (destinationPin?.isOutputLoosely() && to > from + Configuration.distanceThreshold) {
|
||||
this.element.destination = /** @type {KnotPinTemplate} */(destinationPin.template).oppositePin()
|
||||
}
|
||||
}
|
||||
this.#calculateSVGPath(changedProperties)
|
||||
}
|
||||
const dx = Math.max(Math.abs(this.element.fromX - this.element.toX), 1)
|
||||
const dy = Math.max(Math.abs(this.element.fromY - this.element.toY), 1)
|
||||
const width = Math.max(dx, Configuration.linkMinWidth)
|
||||
// const height = Math.max(Math.abs(link.fromY - link.toY), 1)
|
||||
const fillRatio = dx / width
|
||||
const xInverted = this.element.originatesFromInput
|
||||
? this.element.fromX < this.element.toX
|
||||
: this.element.toX < this.element.fromX
|
||||
this.element.startPixels = dx < width // If under minimum width
|
||||
? (width - dx) / 2 // Start from half the empty space
|
||||
: 0 // Otherwise start from the beginning
|
||||
this.element.startPercentage = xInverted ? this.element.startPixels + fillRatio * 100 : this.element.startPixels
|
||||
const c1 =
|
||||
this.element.startPercentage
|
||||
+ (xInverted
|
||||
? LinkTemplate.c1DecreasingValue(width)
|
||||
: 10
|
||||
)
|
||||
* fillRatio
|
||||
const aspectRatio = dy / Math.max(30, dx)
|
||||
const c2 =
|
||||
LinkTemplate.c2Clamped(dx)
|
||||
* LinkTemplate.sigmoidPositive(fillRatio * 1.2 + aspectRatio * 0.5, 1.5, 1.8)
|
||||
+ this.element.startPercentage
|
||||
this.element.svgPathD = Configuration.linkRightSVGPath(this.element.startPercentage, c1, c2)
|
||||
}
|
||||
|
||||
/** @param {PropertyValues} changedProperties */
|
||||
update(changedProperties) {
|
||||
super.update(changedProperties)
|
||||
if (changedProperties.has("originatesFromInput")) {
|
||||
this.element.style.setProperty("--ueb-from-input", this.element.originatesFromInput ? "1" : "0")
|
||||
}
|
||||
const referencePin = this.element.getOutputPin(true)
|
||||
if (referencePin) {
|
||||
this.element.style.setProperty("--ueb-link-color-rgb", LinearColorEntity.printLinearColor(referencePin.color))
|
||||
}
|
||||
this.element.style.setProperty("--ueb-y-reflected", `${this.element.fromY > this.element.toY ? 1 : 0}`)
|
||||
this.element.style.setProperty("--ueb-start-percentage", `${Math.round(this.element.startPercentage)}%`)
|
||||
this.element.style.setProperty("--ueb-link-start", `${Math.round(this.element.startPixels)}`)
|
||||
const mirrorV = (this.element.fromY > this.element.toY ? -1 : 1) // If from is below to => mirror
|
||||
* (this.element.originatesFromInput ? -1 : 1) // Unless from refers to an input pin
|
||||
this.element.style.setProperty("--ueb-link-scale-y", `${mirrorV}`)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { html } from "lit"
|
||||
import Configuration from "../../Configuration.js"
|
||||
import ElementFactory from "../../element/ElementFactory.js"
|
||||
import KnotPinTemplate from "../pin/KnotPinTemplate.js"
|
||||
import NodeTemplate from "./NodeTemplate.js"
|
||||
|
||||
export default class KnotNodeTemplate extends NodeTemplate {
|
||||
|
||||
static #traversedPin = new Set()
|
||||
|
||||
/** @type {Boolean?} */
|
||||
#chainDirection = null // The node is part of a chain connected to an input or output pin
|
||||
#switchDirectionsVisually = false
|
||||
get switchDirectionsVisually() {
|
||||
return this.#switchDirectionsVisually
|
||||
}
|
||||
set switchDirectionsVisually(value) {
|
||||
if (this.#switchDirectionsVisually == value) {
|
||||
return
|
||||
}
|
||||
this.#switchDirectionsVisually = value
|
||||
this.element.acknowledgeReflow()
|
||||
}
|
||||
|
||||
/** @type {PinElement} */
|
||||
#inputPin
|
||||
@@ -29,21 +35,6 @@ export default class KnotNodeTemplate extends NodeTemplate {
|
||||
this.element.classList.add("ueb-node-style-minimal")
|
||||
}
|
||||
|
||||
/** @param {PinElement} startingPin */
|
||||
findDirectionaPin(startingPin) {
|
||||
if (startingPin.isKnot() || KnotNodeTemplate.#traversedPin.has(startingPin)) {
|
||||
KnotNodeTemplate.#traversedPin.clear()
|
||||
return true
|
||||
}
|
||||
KnotNodeTemplate.#traversedPin.add(startingPin)
|
||||
for (let pin of startingPin.getLinks().map(l => this.blueprint.getPin(l))) {
|
||||
if (this.findDirectionaPin(pin)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="ueb-node-border"></div>
|
||||
|
||||
@@ -170,4 +170,11 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
|
||||
}
|
||||
|
||||
linksChanged() { }
|
||||
|
||||
/** All the link connected to this node */
|
||||
getAllConnectedLinks() {
|
||||
const nodeTitle = this.element.nodeTitle
|
||||
const query = `ueb-link[data-origin-node="${nodeTitle}"],ueb-link[data-target-node="${nodeTitle}"]`
|
||||
return /** @type {LinkElement[]} */([...this.blueprint.querySelectorAll(query)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,4 +182,16 @@ export default class PinTemplate extends ITemplate {
|
||||
getClickableElement() {
|
||||
return this.#wrapperElement ?? this.element
|
||||
}
|
||||
|
||||
/** All the link connected to this pin */
|
||||
getAllConnectedLinks() {
|
||||
if (!this.element.isLinked) {
|
||||
return []
|
||||
}
|
||||
const nodeTitle = this.element.nodeElement.nodeTitle
|
||||
const pinId = this.element.pinId
|
||||
const query = `ueb-link[data-origin-node="${nodeTitle}"][data-origin-pin="${pinId}"],`
|
||||
+ `ueb-link[data-target-node="${nodeTitle}"][data-target-pin="${pinId}"]`
|
||||
return /** @type {LinkElement[]} */([...this.blueprint.querySelectorAll(query)])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user