* Add pin and minor style fix WIP

* Fix default pin graphics

* Fix sorting

* Min, max nodes are variadic
This commit is contained in:
barsdeveloper
2023-05-15 22:29:52 +02:00
committed by GitHub
parent 64baa42049
commit c4813d8106
21 changed files with 495 additions and 78 deletions

View File

@@ -157,6 +157,7 @@ export default class Configuration {
select: "/Script/BlueprintGraph.K2Node_Select",
spawnActorFromClass: "/Script/BlueprintGraph.K2Node_SpawnActorFromClass",
switchEnum: "/Script/BlueprintGraph.K2Node_SwitchEnum",
switchGameplayTag: "/Script/GameplayTagsEditor.GameplayTagsK2Node_SwitchGameplayTag",
switchInteger: "/Script/BlueprintGraph.K2Node_SwitchInteger",
switchName: "/Script/BlueprintGraph.K2Node_SwitchName",
switchString: "/Script/BlueprintGraph.K2Node_SwitchString",

View File

@@ -256,6 +256,12 @@ export default class SVGIcon {
</svg>
`
static plusCircle = html`
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M8.00016 10.6667V5.33334M5.3335 8H10.6668M8.00016 1.33334C4.31826 1.33334 1.3335 4.3181 1.3335 8C1.3335 11.6819 4.31826 14.6667 8.00016 14.6667C11.6821 14.6667 14.6668 11.6819 14.6668 8C14.6668 4.3181 11.6821 1.33334 8.00016 1.33334Z"/>
</svg>
`
static questionMark = html`
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 15C9.10456 15 10 14.1046 10 13C10 11.8954 9.10456 11 8 11C6.89544 11 6 11.8954 6 13C6 14.1046 6.89544 15 8 15Z" fill="white" />

View File

@@ -159,11 +159,8 @@ export default class IEntity {
assignAttribute(Utility.sanitize(value, /** @type {AnyValueConstructor<*>} */(defaultType)))
continue // We have a value, need nothing more
}
if (defaultValue !== undefined) {
assignAttribute(Utility.sanitize(
/** @type {AnyValue} */(defaultValue),
/** @type {AnyValueConstructor<AnyValue>} */(defaultType)
))
if (Object.hasOwn(attribute, "default")) { // Accept also explicit undefined
assignAttribute(defaultValue)
}
}
}

View File

@@ -30,18 +30,30 @@ export default class ObjectEntity extends IEntity {
ExportPath: {
type: ObjectReferenceEntity,
},
PinNames: {
type: [String],
default: undefined, // To keep the order, may be defined in additionalPinInserter()
inlined: true,
},
AxisKey: {
type: SymbolEntity,
},
InputAxisKey: {
type: SymbolEntity,
},
NumAdditionalInputs: {
type: Number,
default: undefined, // To keep the order, may be defined in additionalPinInserter()
},
bIsPureFunc: {
type: Boolean,
},
bIsConstFunc: {
type: Boolean,
},
bIsCaseSensitive: {
type: Boolean,
},
VariableReference: {
type: VariableReferenceEntity,
},
@@ -274,10 +286,13 @@ export default class ObjectEntity extends IEntity {
/** @type {ObjectReferenceEntity} */ this.Class
/** @type {String} */ this.Name
/** @type {ObjectReferenceEntity} */ this.ExportPath
/** @type {String[]} */ this.PinNames
/** @type {SymbolEntity?} */ this.AxisKey
/** @type {SymbolEntity?} */ this.InputAxisKey
/** @type {Number?} */ this.NumAdditionalInputs
/** @type {Boolean?} */ this.bIsPureFunc
/** @type {Boolean?} */ this.bIsConstFunc
/** @type {Boolean?} */ this.bIsCaseSensitive
/** @type {VariableReferenceEntity?} */ this.VariableReference
/** @type {SymbolEntity?} */ this.SelfContextInfo
/** @type {String?} */ this.DelegatePropertyName
@@ -635,11 +650,14 @@ export default class ObjectEntity extends IEntity {
let memberName = this.FunctionReference?.MemberName
if (memberName) {
const memberParent = this.FunctionReference.MemberParent?.path ?? ""
if (memberName === "AddKey") {
let result = memberParent.match(ObjectEntity.sequencerScriptingNameRegex)
if (result) {
return `Add Key (${Utility.formatStringName(result[1])})`
}
switch (memberName) {
case "AddKey":
let result = memberParent.match(ObjectEntity.sequencerScriptingNameRegex)
if (result) {
return `Add Key (${Utility.formatStringName(result[1])})`
}
case "Concat_StrStr":
return "Append"
}
const memberNameTraceLineMatch = memberName.match(Configuration.lineTracePattern)
if (memberNameTraceLineMatch) {
@@ -813,4 +831,97 @@ export default class ObjectEntity extends IEntity {
}
return SVGIcon.functionSymbol
}
additionalPinInserter() {
/** @type {() => PinEntity[]} */
let pinEntities
/** @type {(pinEntity: PinEntity) => Number} */
let pinIndexFromEntity
/** @type {(newPinIndex: Number, minIndex: Number, maxIndex: Number) => String} */
let pinNameFromIndex
switch (this.getType()) {
case Configuration.paths.commutativeAssociativeBinaryOperator:
switch (this.FunctionReference?.MemberName) {
case "BMax":
case "BMin":
case "Concat_StrStr":
case "FMax":
case "FMin":
case "Max":
case "MaxInt64":
case "Min":
case "MinInt64":
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isInput())
pinIndexFromEntity ??= pinEntity =>
pinEntity.PinName.match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = String.fromCharCode(index >= 0 ? index : max + "A".charCodeAt(0) + 1)
this.NumAdditionalInputs = pinEntities().length - 1
return result
}
break
}
break
case Configuration.paths.switchInteger:
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*(\d+)\s*$/)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1) => (index < 0 ? max + 1 : index).toString()
break
case Configuration.paths.switchName:
case Configuration.paths.switchString:
pinEntities ??= () => this.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.match(/^\s*Case_(\d+)\s*$/)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1) => {
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`
this.PinNames ??= []
this.PinNames.push(result)
return result
}
break
}
if (pinEntities) {
return () => {
let min = Number.MAX_SAFE_INTEGER
let max = Number.MIN_SAFE_INTEGER
let values = []
const modelPin = pinEntities().reduce((acc, cur) => {
const value = pinIndexFromEntity(cur)
if (!isNaN(value)) {
values.push(value)
min = Math.min(value, min)
if (value > max) {
max = value
return cur
}
} else if (acc === undefined) {
return cur
}
return acc
})
if (min === Number.MAX_SAFE_INTEGER || max === Number.MIN_SAFE_INTEGER) {
min = undefined
max = undefined
}
if (!modelPin) {
return null
}
values.sort((a, b) => a < b ? -1 : a === b ? 0 : 1)
let prev = values[0]
let index = values.findIndex(
// Search for a gap
value => {
const result = value - prev > 1
prev = value
return result
}
)
const newPin = new PinEntity(modelPin)
newPin.PinId = GuidEntity.generateGuid()
newPin.PinName = pinNameFromIndex(index, min, max)
newPin.PinToolTip = undefined
this.CustomProperties.push(newPin)
return newPin
}
}
}
}

View File

@@ -8,6 +8,7 @@ import Utility from "../../Utility.js"
* @typedef {import("../../element/NodeElement.js").default} NodeElement
* @typedef {import("../../element/PinElement.js").default} PinElement
* @typedef {import("../../element/PinElement.js").PinElementConstructor} PinElementConstructor
* @typedef {import("../../entity/PinEntity.js").default} PinEntity
* @typedef {import("lit").PropertyValues} PropertyValues
*/
@@ -16,9 +17,33 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
/** @typedef {typeof NodeTemplate} NodeTemplateConstructor */
static nodeStyleClasses = ["ueb-node-style-default"]
#hasSubtitle = false
static nodeStyleClasses = ["ueb-node-style-default"]
/** @type {() => PinEntity} */
pinInserter
/** @type {HTMLElement} */
inputContainer
/** @type {HTMLElement} */
outputContainer
/** @type {PinElement} */
pinElement
addPinHandler = () => {
const pin = this.pinInserter?.()
if (pin) {
if (this.defaultPin && this.defaultPin.isInput() === pin.isInput()) {
this.defaultPin.before(this.createPinElement(pin))
} else {
(pin.isInput() ? this.inputContainer : this.outputContainer).appendChild(this.createPinElement(pin))
}
this.element.acknowledgeReflow()
}
}
toggleAdvancedDisplayHandler = () => {
this.element.toggleShowAdvancedPinDisplay()
@@ -26,11 +51,23 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
this.element.updateComplete.then(() => this.element.acknowledgeReflow())
}
/** @param {PinEntity} pinEntity */
createPinElement(pinEntity) {
const pinElement = /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin"))
.newObject(pinEntity, undefined, this.element)
if (this.pinInserter && !this.defaultPin && pinElement.getPinName() === "Default") {
this.defaultPin = pinElement
this.defaultPin.classList.add("ueb-node-variadic-default")
}
return pinElement
}
/** @param {NodeElement} element */
initialize(element) {
super.initialize(element)
this.element.classList.add(.../** @type {NodeTemplateConstructor} */(this.constructor).nodeStyleClasses)
this.element.style.setProperty("--ueb-node-color", this.getColor().cssText)
this.pinInserter = this.element.entity.additionalPinInserter()
}
getColor() {
@@ -45,6 +82,11 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
<div class="ueb-node-content">
<div class="ueb-node-inputs"></div>
<div class="ueb-node-outputs"></div>
${this.pinInserter ? html`
<div class="ueb-node-variadic" @click="${this.addPinHandler}">
Add pin ${SVGIcon.plusCircle}
</div>
`: nothing}
</div>
${this.element.entity.isDevelopmentOnly() ? html`
<div class="ueb-node-developmentonly">
@@ -94,25 +136,31 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
/** @param {PropertyValues} changedProperties */
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties)
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())
}
setupPins() {
const inputContainer = /** @type {HTMLElement} */(this.element.querySelector(".ueb-node-inputs"))
const outputContainer = /** @type {HTMLElement} */(this.element.querySelector(".ueb-node-outputs"))
this.element.nodeNameElement = /** @type {HTMLElement} */(this.element.querySelector(".ueb-node-name-text"))
let hasInput = false
let hasOutput = false
this.element.getPinElements().forEach(p => {
for (const p of this.element.getPinElements()) {
if (p === this.defaultPin) {
continue
}
if (p.isInput()) {
inputContainer.appendChild(p)
this.inputContainer.appendChild(p)
hasInput = true
} else if (p.isOutput()) {
outputContainer.appendChild(p)
this.outputContainer.appendChild(p)
hasOutput = true
}
})
}
if (this.defaultPin) {
(this.defaultPin.isInput() ? this.inputContainer : this.outputContainer).appendChild(this.defaultPin)
}
if (hasInput) {
this.element.classList.add("ueb-node-has-inputs")
}
@@ -127,9 +175,7 @@ export default class NodeTemplate extends ISelectableDraggableTemplate {
.map(pinEntity => {
this.#hasSubtitle = this.#hasSubtitle
|| pinEntity.PinName === "self" && pinEntity.pinDisplayName() === "Target"
let pinElement = /** @type {PinElementConstructor} */(ElementFactory.getConstructor("ueb-pin"))
.newObject(pinEntity, undefined, this.element)
return pinElement
return this.createPinElement(pinEntity)
})
}

View File

@@ -1,6 +1,7 @@
import { html, nothing } from "lit"
import ElementFactory from "../../element/ElementFactory.js"
import NodeTemplate from "./NodeTemplate.js"
import SVGIcon from "../../SVGIcon.js"
/**
* @typedef {import("../../element/NodeElement.js").default} NodeElement
@@ -41,6 +42,11 @@ export default class VariableManagementNodeTemplate extends NodeTemplate {
${this.#hasOutput ? html`
<div class="ueb-node-outputs"></div>
` : nothing}
${this.pinInserter ? html`
<div class="ueb-node-variadic" @click="${this.addPinHandler}">
Add pin ${SVGIcon.plusCircle}
</div>
`: nothing}
</div>
</div>
</div>

View File

@@ -16,6 +16,6 @@ export default class ExecPinTemplate extends PinTemplate {
} else if (pinName === "execute" || pinName === "then") {
return html``
}
return html`${this.element.getPinDisplayName()}`
return html`<span class="ueb-pin-name ueb-ellipsis-nowrap-text">${this.element.getPinDisplayName()}</span>`
}
}

View File

@@ -26,7 +26,7 @@ export default class KnotPinTemplate extends MinimalPinTemplate {
: this
)
.iconElement.getBoundingClientRect()
const boundingLocation = [this.element.isInput() ? rect.left : rect.right, (rect.top + rect.bottom) / 2]
const boundingLocation = [this.element.isInput() ? rect.left : rect.right + 1, (rect.top + rect.bottom) / 2]
const location = Utility.convertLocation(boundingLocation, this.blueprint.template.gridElement)
return this.blueprint.compensateTranslation(location[0], location[1])
}

View File

@@ -89,7 +89,7 @@ export default class PinTemplate extends ITemplate {
renderName() {
return html`
<span class="ueb-pin-name">${this.element.getPinDisplayName()}</span>
<span class="ueb-pin-name ueb-ellipsis-nowrap-text">${this.element.getPinDisplayName()}</span>
`
}
@@ -118,7 +118,7 @@ export default class PinTemplate extends ITemplate {
getLinkLocation() {
const rect = this.iconElement.getBoundingClientRect()
const boundingLocation = [this.element.isInput() ? rect.left : rect.right, (rect.top + rect.bottom) / 2]
const boundingLocation = [this.element.isInput() ? rect.left : rect.right + 1, (rect.top + rect.bottom) / 2]
const location = Utility.convertLocation(boundingLocation, this.blueprint.template.gridElement)
return this.blueprint.compensateTranslation(location[0], location[1])
}