Link direction and minor fixes

This commit is contained in:
barsdeveloper
2025-01-27 22:29:52 +02:00
parent dd81356d68
commit 1073691794
11 changed files with 94 additions and 131 deletions

35
.vscode/launch.json vendored
View File

@@ -1,35 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch index.html",
"type": "firefox",
"request": "launch",
"reAttach": true,
"file": "${workspaceFolder}/index.html"
},
{
"name": "Launch localhost",
"type": "firefox",
"request": "launch",
"reAttach": true,
"url": "http://localhost/index.html",
"webRoot": "${workspaceFolder}"
},
{
"name": "Attach",
"type": "firefox",
"request": "attach"
},
{
"name": "Launch WebExtension",
"type": "firefox",
"request": "launch",
"reAttach": true,
"addonPath": "${workspaceFolder}"
}
]
}

63
dist/ueblueprint.js vendored
View File

@@ -7677,14 +7677,14 @@ class LinkTemplate extends IFromToPositionedTemplate {
const to = this.element.targetX; const to = this.element.targetX;
// Switch actual input/output pins if allowed and makes sense // Switch actual input/output pins if allowed and makes sense
if (isOriginAKnot && (!targetPin || isTargetAKnot)) { if (isOriginAKnot && !targetPin) {
if (originPin?.isInputLoosely() && to > from + Configuration.distanceThreshold) { if (originPin?.isInputLoosely() && to > from + Configuration.distanceThreshold) {
this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin(); this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin();
} else if (originPin?.isOutputLoosely() && to < from - Configuration.distanceThreshold) { } else if (originPin?.isOutputLoosely() && to < from - Configuration.distanceThreshold) {
this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin(); this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin();
} }
} }
if (isTargetAKnot && (!originPin || isOriginAKnot)) { if (isTargetAKnot && !originPin) {
if (targetPin?.isInputLoosely() && to < from - Configuration.distanceThreshold) { if (targetPin?.isInputLoosely() && to < from - Configuration.distanceThreshold) {
this.element.target = /** @type {KnotPinTemplate} */(targetPin.template).getoppositePin(); this.element.target = /** @type {KnotPinTemplate} */(targetPin.template).getoppositePin();
} else if (targetPin?.isOutputLoosely() && to > from + Configuration.distanceThreshold) { } else if (targetPin?.isOutputLoosely() && to > from + Configuration.distanceThreshold) {
@@ -7694,16 +7694,11 @@ class LinkTemplate extends IFromToPositionedTemplate {
// Switch visual input/output pins if allowed and makes sense // Switch visual input/output pins if allowed and makes sense
if (originPin && targetPin) { if (originPin && targetPin) {
let directionsCheckedKnot; if (originPin.isKnot() && originPin.hasUpdated) {
if (originPin?.isKnot()) { /** @type {KnotNodeTemplate} */(originPin.nodeElement.template).checkSwtichDirectionsVisually();
// The target end has moved and origin end is a knot
directionsCheckedKnot = originPin.nodeElement;
} else if (targetPin?.isKnot()) {
// The origin end has moved and target end is a knot
directionsCheckedKnot = targetPin.nodeElement;
} }
if (directionsCheckedKnot && directionsCheckedKnot.hasUpdated) { if (targetPin.isKnot() && targetPin.hasUpdated) {
/** @type {KnotNodeTemplate} */(directionsCheckedKnot.template).checkSwtichDirectionsVisually(); /** @type {KnotNodeTemplate} */(targetPin.nodeElement.template).checkSwtichDirectionsVisually();
} }
} }
@@ -8164,12 +8159,12 @@ class LinkElement extends IFromToPositionedElement {
this.origin = pin; this.origin = pin;
} }
/** @param {NodeElement} pin */ /** @param {NodeElement} node */
getOtherPin(pin) { getOtherPin(node) {
if (this.origin?.nodeElement === pin) { if (this.origin?.nodeElement === node) {
return this.target return this.target
} }
if (this.target?.nodeElement === pin) { if (this.target?.nodeElement === node) {
return this.origin return this.origin
} }
} }
@@ -9858,25 +9853,27 @@ class KnotNodeTemplate extends NodeTemplate {
} }
checkSwtichDirectionsVisually() { checkSwtichDirectionsVisually() {
let leftPinsLocation = 0; let leftPinsDelta = 0;
let leftPinsCount = 0; let leftPinsCount = 0;
let rightPinsLocation = 0; let rightPinsDelta = 0;
let rightPinsCount = 0; let rightPinsCount = 0;
const location = this.outputPin.getLinkLocation()[0];
const links = this.getAllConnectedLinks(); const links = this.getAllConnectedLinks();
for (const link of links) { for (const link of links) {
const pin = link.getOtherPin(this.element); const pin = link.getOtherPin(this.element);
const delta = pin.getLinkLocation()[0] - location;
if (pin?.isInput()) { if (pin?.isInput()) {
rightPinsLocation += pin.getLinkLocation()[0]; rightPinsDelta += delta;
++rightPinsCount; ++rightPinsCount;
} else if (pin?.isOutput()) { } else if (pin?.isOutput()) {
leftPinsLocation += pin.getLinkLocation()[0]; leftPinsDelta += delta;
++leftPinsCount; ++leftPinsCount;
} }
} }
leftPinsLocation /= leftPinsCount; leftPinsDelta /= leftPinsCount;
rightPinsLocation /= rightPinsCount; rightPinsDelta /= rightPinsCount;
if ((rightPinsLocation < leftPinsLocation) != this.switchDirectionsVisually) { if ((rightPinsDelta < leftPinsDelta) != this.switchDirectionsVisually) {
this.switchDirectionsVisually = rightPinsLocation < leftPinsLocation; this.switchDirectionsVisually = rightPinsDelta < leftPinsDelta;
} }
} }
} }
@@ -12210,7 +12207,9 @@ class BoolPinTemplate extends PinTemplate {
renderInput() { renderInput() {
return x` return x`
<input type="checkbox" class="ueb-pin-input-wrapper ueb-pin-input" ?checked="${this.element.defaultValue?.valueOf() === true}" /> <input type="checkbox" class="ueb-pin-input-wrapper ueb-pin-input"
?checked="${this.element.defaultValue?.valueOf() === true}"
/>
` `
} }
} }
@@ -12991,7 +12990,8 @@ class LinearColorPinTemplate extends PinTemplate {
renderInput() { renderInput() {
return x` return x`
<span class="ueb-pin-input-wrapper ueb-pin-input" data-linear-color="${this.element.getDefaultValue()?.toString() ?? E}" <span class="ueb-pin-input-wrapper ueb-pin-input"
data-linear-color="${this.element.getDefaultValue()?.toString() ?? E}"
@click="${this.#launchColorPickerWindow}" @click="${this.#launchColorPickerWindow}"
style="--ueb-linear-color: rgba(${this.element.getDefaultValue()?.toString() ?? E})"> style="--ueb-linear-color: rgba(${this.element.getDefaultValue()?.toString() ?? E})">
</span> </span>
@@ -13432,9 +13432,7 @@ class PinElement extends IElement {
/** @type {PinElement} */ /** @type {PinElement} */
let result = this; let result = this;
if (ignoreKnots) { if (ignoreKnots) {
if (ignoreKnots) { return this.#traverseKnots(result)?.isOutput()
return this.#traverseKnots(result)?.isOutput()
}
} }
return result.entity.isOutput() return result.entity.isOutput()
} }
@@ -13510,12 +13508,11 @@ class PinElement extends IElement {
const pinReference = this.createPinReference(); const pinReference = this.createPinReference();
if ( if (
this.isLinked this.isLinked
&& ( && this.entity.isExecution()
this.isInput(true) && this.isOutput(true)
|| this.isOutput(true) && (this.entity.isExecution() || targetPinElement.entity.isExecution()) && this.getLinks().some(ref => !pinReference.equals(ref))
)
&& !this.getLinks().some(ref => pinReference.equals(ref))
) { ) {
if (this.isKnot()) ;
this.unlinkFromAll(); this.unlinkFromAll();
} }
if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) { if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {

File diff suppressed because one or more lines are too long

View File

@@ -283,12 +283,12 @@ export default class LinkElement extends IFromToPositionedElement {
this.origin = pin this.origin = pin
} }
/** @param {NodeElement} pin */ /** @param {NodeElement} node */
getOtherPin(pin) { getOtherPin(node) {
if (this.origin?.nodeElement === pin) { if (this.origin?.nodeElement === node) {
return this.target return this.target
} }
if (this.target?.nodeElement === pin) { if (this.target?.nodeElement === node) {
return this.origin return this.origin
} }
} }

View File

@@ -164,9 +164,7 @@ export default class PinElement extends IElement {
/** @type {PinElement} */ /** @type {PinElement} */
let result = this let result = this
if (ignoreKnots) { if (ignoreKnots) {
if (ignoreKnots) { return this.#traverseKnots(result)?.isOutput()
return this.#traverseKnots(result)?.isOutput()
}
} }
return result.entity.isOutput() return result.entity.isOutput()
} }
@@ -242,12 +240,13 @@ export default class PinElement extends IElement {
const pinReference = this.createPinReference() const pinReference = this.createPinReference()
if ( if (
this.isLinked this.isLinked
&& ( && this.entity.isExecution()
this.isInput(true) && this.isOutput(true)
|| this.isOutput(true) && (this.entity.isExecution() || targetPinElement.entity.isExecution()) && this.getLinks().some(ref => !pinReference.equals(ref))
)
&& !this.getLinks().some(ref => pinReference.equals(ref))
) { ) {
if (this.isKnot()) {
}
this.unlinkFromAll() this.unlinkFromAll()
} }
if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) { if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {

View File

@@ -72,14 +72,14 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
const to = this.element.targetX const to = this.element.targetX
// Switch actual input/output pins if allowed and makes sense // Switch actual input/output pins if allowed and makes sense
if (isOriginAKnot && (!targetPin || isTargetAKnot)) { if (isOriginAKnot && !targetPin) {
if (originPin?.isInputLoosely() && to > from + Configuration.distanceThreshold) { if (originPin?.isInputLoosely() && to > from + Configuration.distanceThreshold) {
this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin() this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin()
} else if (originPin?.isOutputLoosely() && to < from - Configuration.distanceThreshold) { } else if (originPin?.isOutputLoosely() && to < from - Configuration.distanceThreshold) {
this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin() this.element.origin = /** @type {KnotPinTemplate} */(originPin.template).getoppositePin()
} }
} }
if (isTargetAKnot && (!originPin || isOriginAKnot)) { if (isTargetAKnot && !originPin) {
if (targetPin?.isInputLoosely() && to < from - Configuration.distanceThreshold) { if (targetPin?.isInputLoosely() && to < from - Configuration.distanceThreshold) {
this.element.target = /** @type {KnotPinTemplate} */(targetPin.template).getoppositePin() this.element.target = /** @type {KnotPinTemplate} */(targetPin.template).getoppositePin()
} else if (targetPin?.isOutputLoosely() && to > from + Configuration.distanceThreshold) { } else if (targetPin?.isOutputLoosely() && to > from + Configuration.distanceThreshold) {
@@ -90,15 +90,11 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
// Switch visual input/output pins if allowed and makes sense // Switch visual input/output pins if allowed and makes sense
if (originPin && targetPin) { if (originPin && targetPin) {
let directionsCheckedKnot let directionsCheckedKnot
if (originPin?.isKnot()) { if (originPin.isKnot() && originPin.hasUpdated) {
// The target end has moved and origin end is a knot /** @type {KnotNodeTemplate} */(originPin.nodeElement.template).checkSwtichDirectionsVisually()
directionsCheckedKnot = originPin.nodeElement
} else if (targetPin?.isKnot()) {
// The origin end has moved and target end is a knot
directionsCheckedKnot = targetPin.nodeElement
} }
if (directionsCheckedKnot && directionsCheckedKnot.hasUpdated) { if (targetPin.isKnot() && targetPin.hasUpdated) {
/** @type {KnotNodeTemplate} */(directionsCheckedKnot.template).checkSwtichDirectionsVisually() /** @type {KnotNodeTemplate} */(targetPin.nodeElement.template).checkSwtichDirectionsVisually()
} }
} }

View File

@@ -60,25 +60,27 @@ export default class KnotNodeTemplate extends NodeTemplate {
} }
checkSwtichDirectionsVisually() { checkSwtichDirectionsVisually() {
let leftPinsLocation = 0 let leftPinsDelta = 0
let leftPinsCount = 0 let leftPinsCount = 0
let rightPinsLocation = 0 let rightPinsDelta = 0
let rightPinsCount = 0 let rightPinsCount = 0
const location = this.outputPin.getLinkLocation()[0]
const links = this.getAllConnectedLinks() const links = this.getAllConnectedLinks()
for (const link of links) { for (const link of links) {
const pin = link.getOtherPin(this.element) const pin = link.getOtherPin(this.element)
const delta = pin.getLinkLocation()[0] - location
if (pin?.isInput()) { if (pin?.isInput()) {
rightPinsLocation += pin.getLinkLocation()[0] rightPinsDelta += delta
++rightPinsCount ++rightPinsCount
} else if (pin?.isOutput()) { } else if (pin?.isOutput()) {
leftPinsLocation += pin.getLinkLocation()[0] leftPinsDelta += delta
++leftPinsCount ++leftPinsCount
} }
} }
leftPinsLocation /= leftPinsCount leftPinsDelta /= leftPinsCount
rightPinsLocation /= rightPinsCount rightPinsDelta /= rightPinsCount
if ((rightPinsLocation < leftPinsLocation) != this.switchDirectionsVisually) { if ((rightPinsDelta < leftPinsDelta) != this.switchDirectionsVisually) {
this.switchDirectionsVisually = rightPinsLocation < leftPinsLocation this.switchDirectionsVisually = rightPinsDelta < leftPinsDelta
} }
} }
} }

View File

@@ -40,7 +40,9 @@ export default class BoolPinTemplate extends PinTemplate {
renderInput() { renderInput() {
return html` return html`
<input type="checkbox" class="ueb-pin-input-wrapper ueb-pin-input" ?checked="${this.element.defaultValue?.valueOf() === true}" /> <input type="checkbox" class="ueb-pin-input-wrapper ueb-pin-input"
?checked="${this.element.defaultValue?.valueOf() === true}"
/>
` `
} }
} }

View File

@@ -42,7 +42,8 @@ export default class LinearColorPinTemplate extends PinTemplate {
renderInput() { renderInput() {
return html` return html`
<span class="ueb-pin-input-wrapper ueb-pin-input" data-linear-color="${this.element.getDefaultValue()?.toString() ?? nothing}" <span class="ueb-pin-input-wrapper ueb-pin-input"
data-linear-color="${this.element.getDefaultValue()?.toString() ?? nothing}"
@click="${this.#launchColorPickerWindow}" @click="${this.#launchColorPickerWindow}"
style="--ueb-linear-color: rgba(${this.element.getDefaultValue()?.toString() ?? nothing})"> style="--ueb-linear-color: rgba(${this.element.getDefaultValue()?.toString() ?? nothing})">
</span> </span>

View File

@@ -2,35 +2,6 @@ import { expect, test } from "./fixtures/test.js"
const firstRowOnly = v => v.replaceAll(/^\s+|\n.+/gs, "") const firstRowOnly = v => v.replaceAll(/^\s+|\n.+/gs, "")
test.describe.configure({ mode: "parallel" })
test("Renaming", async ({ blueprintPage }) => {
let source = String.raw`
Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="MaterialGraphNode_40" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.MaterialGraphNode_40"'
End Object
`
await blueprintPage.paste(source)
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="MaterialGraphNode_40" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.MaterialGraphNode_40"'`
)
await blueprintPage.node.evaluate(n => n.entity.Name.value = "new name")
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="new name" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.new name"'`
)
await blueprintPage.node.evaluate(n => n.entity.Name = new (n.entity.constructor.attributes.Name)("new name 2"))
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="new name 2" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.new name 2"'`
)
await blueprintPage.node.evaluate(n => n.entity.Name = new (n.entity.constructor.attributes.Name)())
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0."'`
)
await blueprintPage.node.evaluate(n => delete n.entity.Name)
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0."'`
)
})
test("Inner renaming", async ({ blueprintPage }) => { test("Inner renaming", async ({ blueprintPage }) => {
let source = String.raw` let source = String.raw`
Begin Object Class=/Script/PCGEditor.PCGEditorGraphNode Name="PCGEditorGraphNode_2" ExportPath=/Script/PCGEditor.PCGEditorGraphNode'"/Game/NewPCGGraph.NewPCGGraph:PCGEditorGraph_1.PCGEditorGraphNode_2"' Begin Object Class=/Script/PCGEditor.PCGEditorGraphNode Name="PCGEditorGraphNode_2" ExportPath=/Script/PCGEditor.PCGEditorGraphNode'"/Game/NewPCGGraph.NewPCGGraph:PCGEditorGraph_1.PCGEditorGraphNode_2"'

View File

@@ -0,0 +1,30 @@
import { expect, test } from "./fixtures/test.js"
const firstRowOnly = v => v.replaceAll(/^\s+|\n.+/gs, "")
test("Renaming", async ({ blueprintPage }) => {
let source = String.raw`
Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="MaterialGraphNode_40" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.MaterialGraphNode_40"'
End Object
`
await blueprintPage.paste(source)
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="MaterialGraphNode_40" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.MaterialGraphNode_40"'`
)
await blueprintPage.node.evaluate(n => n.entity.Name.value = "new name")
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="new name" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.new name"'`
)
await blueprintPage.node.evaluate(n => n.entity.Name = new (n.entity.constructor.attributes.Name)("new name 2"))
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="new name 2" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0.new name 2"'`
)
await blueprintPage.node.evaluate(n => n.entity.Name = new (n.entity.constructor.attributes.Name)())
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode Name="" ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0."'`
)
await blueprintPage.node.evaluate(n => delete n.entity.Name)
expect(firstRowOnly(await blueprintPage.getSerializedNodes())).toEqual(
`Begin Object Class=/Script/UnrealEd.MaterialGraphNode ExportPath=/Script/UnrealEd.MaterialGraphNode'"/Engine/Transient.M_Brick_Cut_Stone:MaterialGraph_0."'`
)
})