Refactoring WIP

This commit is contained in:
barsdeveloper
2025-02-04 23:11:46 +02:00
parent 0e2ecdf93e
commit 2f357818f4
23 changed files with 582 additions and 232 deletions

View File

@@ -85,7 +85,6 @@ export default class Blueprint extends IElement {
nodesNames = new Map()
/** @type {Coordinates} */
mousePosition = [0, 0]
waitingExpandUpdate = false
constructor() {
super()

View File

@@ -93,9 +93,9 @@ export default class Configuration {
static mouseWheelZoomThreshold = 80
static nodeDragEventName = "ueb-node-drag"
static nodeDragGeneralEventName = "ueb-node-drag-general"
static nodeTitle = (name, counter) => `${name}_${counter}`
static nodeRadius = 8 // px
static nodeReflowEventName = "ueb-node-reflow"
static nodeTitle = (name, counter) => `${name}_${counter}`
static nodeUpdateEventName = "ueb-node-update"
static paths = {
actorBoundEvent: "/Script/BlueprintGraph.K2Node_ActorBoundEvent",
addDelegate: "/Script/BlueprintGraph.K2Node_AddDelegate",
@@ -215,6 +215,7 @@ export default class Configuration {
timeline: "/Script/BlueprintGraph.K2Node_Timeline",
timeManagementBlueprintLibrary: "/Script/TimeManagement.TimeManagementBlueprintLibrary",
transform: "/Script/CoreUObject.Transform",
typedElementHandleLibrary: "/Script/TypedElementFramework.TypedElementHandleLibrary",
userDefinedEnum: "/Script/Engine.UserDefinedEnum",
variableGet: "/Script/BlueprintGraph.K2Node_VariableGet",
variableSet: "/Script/BlueprintGraph.K2Node_VariableSet",
@@ -226,6 +227,7 @@ export default class Configuration {
whileLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:WhileLoop",
}
static pinInputWrapWidth = 145 // px
static pinUpdateEventName = "ueb-pin-update"
static removeEventName = "ueb-element-delete"
static scale = {
[-12]: 0.133333,

View File

@@ -88,9 +88,10 @@ export default function nodeTemplateClass(nodeEntity) {
const memberName = nodeEntity.FunctionReference?.MemberName?.toString()
if (
memberName && (
memberParent === paths.kismetMathLibrary
|| memberParent === paths.kismetArrayLibrary
memberParent === paths.kismetArrayLibrary
|| memberParent === paths.kismetMathLibrary
|| memberParent === paths.kismetStringLibrary
|| memberParent === paths.typedElementHandleLibrary
)) {
if (memberName.startsWith("Conv_")) {
return VariableConversionNodeTemplate
@@ -117,6 +118,7 @@ export default function nodeTemplateClass(nodeEntity) {
case "BMin":
case "CrossProduct2D":
case "DotProduct2D":
case "Equal":
case "Exp":
case "FMax":
case "FMin":

View File

@@ -375,6 +375,7 @@ export default function nodeTitle(entity) {
case paths.kismetStringLibrary:
case paths.slateBlueprintLibrary:
case paths.timeManagementBlueprintLibrary:
case paths.typedElementHandleLibrary:
const leadingLetter = memberName.match(/[BF]([A-Z]\w+)/)
if (leadingLetter) {
// Some functions start with B or F (Like FCeil, FMax, BMin)
@@ -385,6 +386,7 @@ export default function nodeTitle(entity) {
case "BooleanAND": return "AND"
case "BooleanNAND": return "NAND"
case "BooleanOR": return "OR"
case "Equal": return "=="
case "Exp": return "e"
case "LineTraceSingle": return "Line Trace By Channel"
case "Max": return "MAX"

View File

@@ -5,6 +5,7 @@ import Utility from "../Utility.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import LinkTemplate from "../template/LinkTemplate.js"
import IFromToPositionedElement from "./IFromToPositionedElement.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
/** @extends {IFromToPositionedElement<Object, LinkTemplate>} */
export default class LinkElement extends IFromToPositionedElement {
@@ -43,6 +44,9 @@ export default class LinkElement extends IFromToPositionedElement {
converter: BooleanEntity.booleanConverter,
reflect: true,
},
color: {
type: LinearColorEntity,
},
svgPathD: {
type: String,
attribute: false,
@@ -75,6 +79,29 @@ export default class LinkElement extends IFromToPositionedElement {
this.#setPin(pin, true)
}
/** @param {UEBNodeUpdateEvent} e */
#nodeUpdateHandler = e => {
if (this.#origin.nodeElement === e.target) {
if (this.originNode != this.#origin.nodeElement.nodeTitle) {
this.originNode = this.#origin.nodeElement.nodeTitle
}
this.setOriginLocation()
} else if (this.#target.nodeElement === e.target) {
if (this.targetNode != this.#target.nodeElement.nodeTitle) {
this.targetNode = this.#target.nodeElement.nodeTitle
}
this.setTargetLocation()
} else {
throw new Error("Unexpected node update")
}
}
/** @param {UEBNodeUpdateEvent} e */
#pinUpdateHandler = e => {
const colorReferencePin = this.getOutputPin(true)
if (!this.color?.equals(colorReferencePin.color)) {
this.color = colorReferencePin.color
}
}
#nodeDeleteHandler = () => this.remove()
/** @param {UEBDragEvent} e */
#nodeDragOriginHandler = e => this.addOriginLocation(...e.detail.value)
@@ -104,6 +131,7 @@ export default class LinkElement extends IFromToPositionedElement {
this.targetNode = ""
this.targetPin = ""
this.originatesFromInput = false
this.color = new LinearColorEntity()
this.startPercentage = 0
this.svgPathD = ""
this.startPixels = 0
@@ -153,15 +181,13 @@ export default class LinkElement extends IFromToPositionedElement {
}
if (getCurrentPin()) {
const nodeElement = getCurrentPin().getNodeElement()
nodeElement.removeEventListener(Configuration.nodeUpdateEventName, this.#nodeUpdateHandler)
nodeElement.removeEventListener(Configuration.removeEventName, this.#nodeDeleteHandler)
nodeElement.removeEventListener(
Configuration.nodeDragEventName,
isTargetPin ? this.#nodeDragTargetHandler : this.#nodeDragOriginHandler
)
nodeElement.removeEventListener(
Configuration.nodeReflowEventName,
isTargetPin ? this.#nodeReflowTargetHandler : this.#nodeReflowOriginHandler
)
getCurrentPin().removeEventListener(Configuration.pinUpdateEventName, this.#pinUpdateHandler)
this.#unlinkPins()
}
if (isTargetPin) {
@@ -175,20 +201,20 @@ export default class LinkElement extends IFromToPositionedElement {
}
if (getCurrentPin()) {
const nodeElement = getCurrentPin().getNodeElement()
nodeElement.addEventListener(Configuration.nodeUpdateEventName, this.#nodeUpdateHandler)
nodeElement.addEventListener(Configuration.pinUpdateEventName, this.#pinUpdateHandler)
nodeElement.addEventListener(Configuration.removeEventName, this.#nodeDeleteHandler)
nodeElement.addEventListener(
Configuration.nodeDragEventName,
isTargetPin ? this.#nodeDragTargetHandler : this.#nodeDragOriginHandler
)
nodeElement.addEventListener(
Configuration.nodeReflowEventName,
isTargetPin ? this.#nodeReflowTargetHandler : this.#nodeReflowOriginHandler
)
getCurrentPin().addEventListener(Configuration.pinUpdateEventName, this.#pinUpdateHandler)
isTargetPin
? this.setTargetLocation()
: (this.setOriginLocation(), this.originatesFromInput = this.origin.isInputVisually())
this.#linkPins()
}
this.color = this.getOutputPin(true)?.color
}
#linkPins() {

View File

@@ -138,18 +138,11 @@ export default class NodeElement extends ISelectableDraggableElement {
this.#redirectLinksBeforeRename(newName?.toString())
this.nodeTitle = newName?.toString()
this.nodeDisplayName = nodeTitle(entity)
this.acknowledgeUpdate()
}
)
}
async getUpdateComplete() {
let result = await super.getUpdateComplete()
for (const pin of this.getPinElements()) {
result &&= await pin.updateComplete
}
return result
}
/** @param {NodeElement} commentNode */
bindToComment(commentNode) {
if (commentNode != this && !this.boundComments.includes(commentNode)) {
@@ -192,14 +185,14 @@ export default class NodeElement extends ISelectableDraggableElement {
setNodeWidth(value) {
this.entity.setNodeWidth(value)
this.sizeX = value
this.acknowledgeReflow()
this.acknowledgeUpdate(true)
}
/** @param {Number} value */
setNodeHeight(value) {
this.entity.setNodeHeight(value)
this.sizeY = value
this.acknowledgeReflow()
this.acknowledgeUpdate(true)
}
/** @param {IElement[]} nodesWhitelist */
@@ -222,11 +215,13 @@ export default class NodeElement extends ISelectableDraggableElement {
super.setLocation(x, y, acknowledge)
}
acknowledgeReflow() {
this.requestUpdate()
this.updateComplete.then(() => this.computeSizes())
let reflowEvent = new CustomEvent(Configuration.nodeReflowEventName)
this.dispatchEvent(reflowEvent)
acknowledgeUpdate(resize = false) {
const event = new CustomEvent(Configuration.nodeUpdateEventName)
if (resize) {
this.requestUpdate()
this.updateComplete.then(() => this.computeSizes())
}
this.dispatchEvent(event)
}
setShowAdvancedPinDisplay(value) {

View File

@@ -44,7 +44,8 @@ export default class PinElement extends IElement {
fromAttribute: (value, type) => value
? LinearColorEntity.getLinearColorFromAnyFormat().parse(value)
: null,
toAttribute: (value, type) => value ? LinearColorEntity.printLinearColor(value) : null,
/** @param {LinearColorEntity} value */
toAttribute: (value, type) => value?.toString() ?? "",
},
attribute: "data-color",
reflect: true,
@@ -96,10 +97,11 @@ export default class PinElement extends IElement {
this.connectable = !entity.bNotConnectable?.valueOf()
super.initialize(entity, template)
this.pinId = this.entity.PinId
this.pinType = this.entity.getType()
this.updateType()
this.defaultValue = this.entity.getDefaultValue()
this.pinDirection = entity.isInput() ? "input" : entity.isOutput() ? "output" : "hidden"
this.updateColor()
/** @type {LinearColorEntity} */
this.color = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
}
setup() {
@@ -107,8 +109,13 @@ export default class PinElement extends IElement {
this.nodeElement = this.closest("ueb-node")
}
updateColor() {
this.color = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
updateType() {
this.pinType = this.entity.getType()
const newColor = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
if (!this.color?.equals(newColor)) {
this.color = newColor
this.acknowledgeUpdate()
}
}
createPinReference() {
@@ -289,4 +296,9 @@ export default class PinElement extends IElement {
}
return false
}
acknowledgeUpdate() {
let event = new CustomEvent(Configuration.pinUpdateEventName)
this.dispatchEvent(event)
}
}

View File

@@ -34,6 +34,8 @@ import Vector2DEntity from "./Vector2DEntity.js"
import Vector4DEntity from "./Vector4DEntity.js"
import VectorEntity from "./VectorEntity.js"
const paths = Configuration.paths
/** @template {IEntity} T */
export default class PinEntity extends IEntity {
@@ -49,24 +51,24 @@ export default class PinEntity extends IEntity {
"name": StringEntity,
"real": NumberEntity,
"string": StringEntity,
[Configuration.paths.linearColor]: LinearColorEntity,
[Configuration.paths.niagaraBool]: BooleanEntity,
[Configuration.paths.niagaraFloat]: NumberEntity,
[Configuration.paths.niagaraPosition]: VectorEntity,
[Configuration.paths.rotator]: RotatorEntity,
[Configuration.paths.vector]: VectorEntity,
[Configuration.paths.vector2D]: Vector2DEntity,
[Configuration.paths.vector4f]: Vector4DEntity,
[paths.linearColor]: LinearColorEntity,
[paths.niagaraBool]: BooleanEntity,
[paths.niagaraFloat]: NumberEntity,
[paths.niagaraPosition]: VectorEntity,
[paths.rotator]: RotatorEntity,
[paths.vector]: VectorEntity,
[paths.vector2D]: Vector2DEntity,
[paths.vector4f]: Vector4DEntity,
}
static #alternativeTypeEntityMap = {
"enum": EnumDisplayValueEntity,
"rg": RBSerializationVector2DEntity,
[Configuration.paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
[Configuration.paths.rotator]: SimpleSerializationRotatorEntity,
[Configuration.paths.vector]: SimpleSerializationVectorEntity,
[Configuration.paths.vector2D]: SimpleSerializationVector2DEntity,
[Configuration.paths.vector3f]: SimpleSerializationVectorEntity,
[Configuration.paths.vector4f]: SimpleSerializationVector4DEntity,
[paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
[paths.rotator]: SimpleSerializationRotatorEntity,
[paths.vector]: SimpleSerializationVectorEntity,
[paths.vector2D]: SimpleSerializationVector2DEntity,
[paths.vector3f]: SimpleSerializationVectorEntity,
[paths.vector4f]: SimpleSerializationVector4DEntity,
}
static attributes = {
PinId: GuidEntity.withDefault(),
@@ -216,9 +218,9 @@ export default class PinEntity extends IEntity {
case "rg":
return "rg"
case "rgb":
return Configuration.paths.vector
return paths.vector
case "rgba":
return Configuration.paths.linearColor
return paths.linearColor
default:
return subCategory
}
@@ -254,14 +256,14 @@ export default class PinEntity extends IEntity {
isEnum() {
const type = this.PinType.PinSubCategoryObject?.type
return type === Configuration.paths.enum
|| type === Configuration.paths.userDefinedEnum
return type === paths.enum
|| type === paths.userDefinedEnum
|| type?.toLowerCase() === "enum"
}
isExecution() {
return this.PinType.PinCategory.toString() === "exec"
|| this.getType() === Configuration.paths.niagaraParameterMap
|| this.getType() === paths.niagaraParameterMap
}
isHidden() {

View File

@@ -1,7 +1,6 @@
import { html, nothing } from "lit"
import Configuration from "../Configuration.js"
import ElementFactory from "../element/ElementFactory.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import KnotEntity from "../entity/objects/KnotEntity.js"
import KeyboardShortcut from "../input/keyboard/KeyboardShortcut.js"
import MouseClick from "../input/mouse/MouseClick.js"
@@ -53,8 +52,6 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
this.blueprint.addGraphElement(knot) // Important: keep it before changing existing links
const inputPin = this.element.getInputPin()
const outputPin = this.element.getOutputPin()
this.element.origin = null
this.element.target = null
const link = /** @type {LinkElementConstructor} */(ElementFactory.getConstructor("ueb-link"))
.newObject(outputPin, knotTemplate.inputPin)
this.blueprint.addGraphElement(link)
@@ -181,24 +178,28 @@ export default class LinkTemplate extends IFromToPositionedTemplate {
/** @param {PropertyValues} changedProperties */
update(changedProperties) {
super.update(changedProperties)
const referencePin = this.element.getOutputPin(true)
if (referencePin) {
this.element.style.setProperty("--ueb-link-color-rgb", LinearColorEntity.printLinearColor(referencePin.color))
const style = this.element.style
if (changedProperties.has("color")) {
style.setProperty("--ueb-link-color-rgb", this.element.color?.toString() ?? "255, 255, 255")
}
this.element.style.setProperty("--ueb-start-percentage", `${Math.round(this.element.startPercentage)}%`)
this.element.style.setProperty("--ueb-link-start", `${Math.round(this.element.startPixels)}`)
style.setProperty("--ueb-start-percentage", `${Math.round(this.element.startPercentage)}%`)
style.setProperty("--ueb-link-start", `${Math.round(this.element.startPixels)}`)
const mirrorV = (this.element.originY > this.element.targetY ? -1 : 1) // If from is below to => mirror
* (this.element.originatesFromInput ? -1 : 1) // Unless fro refers to an input pin
* (this.element.origin?.isInputVisually() && this.element.target?.isInputVisually() ? -1 : 1)
const mirrorH = (this.element.origin?.isInputVisually() && this.element.target?.isInputVisually() ? -1 : 1)
this.element.style.setProperty("--ueb-link-scale-y", `${mirrorV}`)
this.element.style.setProperty("--ueb-link-scale-x", `${mirrorH}`)
style.setProperty("--ueb-link-scale-y", `${mirrorV}`)
style.setProperty("--ueb-link-scale-x", `${mirrorH}`)
}
render() {
return html`
<svg version="1.2" baseProfile="tiny" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
<path id="${this.#uniqueId}" fill="none" vector-effect="non-scaling-stroke" d="${this.element.svgPathD}" />
<svg version="1.2" baseProfile="tiny" width="100%" height="100%" viewBox="0 0 100 100"
preserveAspectRatio="none"
>
<path id="${this.#uniqueId}" fill="none" vector-effect="non-scaling-stroke"
d="${this.element.svgPathD}"
/>
<use href="#${this.#uniqueId}" class="ueb-link-area" pointer-events="all" />
<use href="#${this.#uniqueId}" class="ueb-link-path" pointer-events="none" />
</svg>

View File

@@ -14,7 +14,7 @@ export default class KnotNodeTemplate extends NodeTemplate {
return
}
this.#switchDirectionsVisually = value
this.element.acknowledgeReflow()
this.element.acknowledgeUpdate()
}
/** @type {PinElement} */
@@ -35,17 +35,6 @@ export default class KnotNodeTemplate extends NodeTemplate {
this.element.classList.add("ueb-node-style-minimal")
}
/** @param {PropertyValues} changedProperties */
update(changedProperties) {
super.update(changedProperties)
if (!this.#inputPin.isLinked && !this.#outputPin.isLinked) {
this.#inputPin.entity.PinType.PinCategory.value = "wildcard"
this.#inputPin.updateColor()
this.#outputPin.entity.PinType.PinCategory.value = "wildcard"
this.#inputPin.updateColor()
}
}
render() {
return html`
<div class="ueb-node-border"></div>

View File

@@ -31,14 +31,14 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
} else {
(pin.isInput() ? this.inputContainer : this.outputContainer).appendChild(this.createPinElement(pin))
}
this.element.acknowledgeReflow()
this.element.acknowledgeUpdate()
}
}
toggleAdvancedDisplayHandler = () => {
this.element.toggleShowAdvancedPinDisplay()
this.element.requestUpdate()
this.element.updateComplete.then(() => this.element.acknowledgeReflow())
this.element.updateComplete.then(() => this.element.acknowledgeUpdate())
}
/** @param {PinEntity<IEntity>} pinEntity */
@@ -125,7 +125,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
this.inputContainer = this.element.querySelector(".ueb-node-inputs")
this.outputContainer = this.element.querySelector(".ueb-node-outputs")
this.setupPins()
this.element.updateComplete.then(() => this.element.acknowledgeReflow())
this.element.updateComplete.then(() => this.element.acknowledgeUpdate())
}
setupPins() {

View File

@@ -78,13 +78,13 @@ export default class IInputPinTemplate extends PinTemplate {
}
if (Self.canWrapInput && this.isInputRendered()) {
this.element.addEventListener("input", this.#checkWrapHandler)
this.element.nodeElement.addEventListener(Configuration.nodeReflowEventName, this.#checkWrapHandler)
this.element.nodeElement.addEventListener(Configuration.nodeUpdateEventName, this.#checkWrapHandler)
}
}
cleanup() {
super.cleanup()
this.element.nodeElement.removeEventListener(Configuration.nodeReflowEventName, this.#checkWrapHandler)
this.element.nodeElement.removeEventListener(Configuration.nodeUpdateEventName, this.#checkWrapHandler)
this.element.removeEventListener("input", this.#checkWrapHandler)
this.element.removeEventListener("input", this.#setInput)
this.element.removeEventListener("focusout", this.#setInput)
@@ -112,7 +112,7 @@ export default class IInputPinTemplate extends PinTemplate {
this.setDefaultValue(values.map(v => IInputPinTemplate.stringFromInputToUE(v)), values)
}
this.element.requestUpdate()
this.element.nodeElement.acknowledgeReflow()
this.element.updateComplete.then(() => this.element.nodeElement.acknowledgeUpdate())
}
setDefaultValue(values = [], rawValues = values) {

View File

@@ -1,13 +1,68 @@
import { html } from "lit"
import FunctionReferenceEntity from "../../entity/FunctionReferenceEntity.js"
import ObjectReferenceEntity from "../../entity/ObjectReferenceEntity.js"
import PinTypeEntity from "../../entity/PinTypeEntity.js"
import StringEntity from "../../entity/StringEntity.js"
import MinimalPinTemplate from "./MinimalPinTemplate.js"
/** @extends MinimalPinTemplate<KnotEntity> */
export default class KnotPinTemplate extends MinimalPinTemplate {
static #wildcardPinType = new PinTypeEntity({
PinCategory: new StringEntity("wildcard"),
PinSubCategoryObject: ObjectReferenceEntity.createNoneInstance(),
PinSubCategoryMemberReference: new FunctionReferenceEntity(),
})
/** @param {PinTypeEntity} type */
#setType(type) {
const oppositePin = this.getoppositePin()
this.element.entity.PinType.copyTypeFrom(type)
oppositePin.entity.PinType.copyTypeFrom(type)
this.element.updateType()
oppositePin.updateType()
}
render() {
return this.element.isOutput() ? super.render() : html``
}
/** @param {PropertyValues} changedProperties */
update(changedProperties) {
super.update(changedProperties)
if (changedProperties.has("isLinked")) {
const oppositePin = this.getoppositePin()
if (!this.element.isLinked && !oppositePin.isLinked) {
this.#setType(KnotPinTemplate.#wildcardPinType)
} else if (this.element.isLinked && this.element.pinType == "wildcard") {
const type = this.element
.getLinks()
.map(r => this.blueprint.getPin(r))
.find(p => p && p.pinType != "wildcard")
?.entity
.PinType
if (type) {
/** @type {KnotPinTemplate[]} */
const propagated = [this]
for (let i = 0; i < propagated.length; ++i) {
let current = propagated[i]
current.#setType(type)
current = /** @type {KnotPinTemplate} */(current.getoppositePin().template)
current.#setType(type)
propagated.push(
...current.element.getLinks().map(r => (
/** @type {KnotPinTemplate} */(
this.blueprint.getPin(r).template
)
))
)
}
}
}
}
}
getoppositePin() {
const nodeTemplate = /** @type {KnotNodeTemplate} */(this.element.nodeElement.template)
return this.element.isOutput() ? nodeTemplate.inputPin : nodeTemplate.outputPin

View File

@@ -159,14 +159,16 @@ export default class PinTemplate extends ITemplate {
// When connected, an input may drop its input fields which means the node has to reflow
const node = this.element.nodeElement
this.element.requestUpdate()
this.element.updateComplete.then(() => node.acknowledgeReflow())
this.element.updateComplete.then(() => node.acknowledgeUpdate())
}
if (changedProperties.has("color")) {
this.element.style.setProperty("--ueb-pin-color-rgb", this.element.color.toString())
}
}
/** @param {PropertyValues} changedProperties */
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties)
this.element.style.setProperty("--ueb-pin-color-rgb", this.element.entity.pinColor().cssText)
this.#iconElement = this.element.querySelector(".ueb-pin-icon svg") ?? this.element
this.#wrapperElement = this.element.querySelector(".ueb-pin-wrapper")
}