using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Experimental.GraphView; using UnityEditor.VFX.Block; using UnityEngine; using UnityEngine.VFX; using UnityEngine.UIElements; using UnityEngine.Profiling; using PositionType = UnityEngine.UIElements.Position; namespace UnityEditor.VFX.UI { class VFXContextUI : VFXNodeUI { // TODO: Unused except for debugging readonly CustomStyleProperty RectColorProperty = new CustomStyleProperty("--rect-color"); Image m_HeaderIcon; Label m_HeaderSpace; Label m_Subtitle; Label m_Footer; VisualElement m_FlowInputConnectorContainer; VisualElement m_FlowOutputConnectorContainer; VisualElement m_BlockContainer; VisualElement m_NoBlock; VisualElement m_DragDisplay; Label m_Label; TextField m_TextField; public new VFXContextController controller { get { return base.controller as VFXContextController; } } protected override void OnNewController() { m_CanHaveBlocks = controller.model.CanHaveBlocks(); } public bool canHaveBlocks { get => m_CanHaveBlocks; } public static string ContextEnumToClassName(string name) { if (name[0] == 'k') { Debug.LogError("Fix this since k should have been removed from enums"); } return name.ToLower(); } public void UpdateLabel() { var graph = controller.model.GetGraph(); if (graph != null && controller.model.contextType == VFXContextType.Spawner) m_Label.text = graph.systemNames.GetUniqueSystemName(controller.model.GetData()); else m_Label.text = controller.model.label; } protected override void SelfChange() { base.SelfChange(); Profiler.BeginSample("VFXContextUI.CreateBlockProvider"); if (m_BlockProvider == null) { m_BlockProvider = new VFXBlockProvider(controller, (variant, mPos) => { if (variant.modelType != typeof(VisualEffectSubgraphBlock)) { UpdateSelectionWithNewBlocks(); AddBlock(mPos, variant); } else { var path = variant.settings.Single(x => x.Key == "path").Value as string; var subgraphBlock = AssetDatabase.LoadAssetAtPath(path); var view = GetFirstAncestorOfType(); var graph = subgraphBlock.GetResource().GetOrCreateGraph(); if (view.HasCustomAttributeConflicts(graph.attributesManager.GetCustomAttributes())) { return; } // Prevent cyclic recursion if (controller.model.GetGraph() == graph) { Debug.LogWarning("Cannot add this subgraph because it would create a cyclic recursion"); return; } int blockIndex = GetDragBlockIndex(mPos); VFXBlock newModel = ScriptableObject.CreateInstance(); newModel.SetSettingValue("m_Subgraph", subgraphBlock); UpdateSelectionWithNewBlocks(); using var growContext = new GrowContext(this); controller.AddBlock(blockIndex, newModel, true); } }); } Profiler.EndSample(); if (inputContainer.childCount == 0 && !hasSettings) { mainContainer.AddToClassList("empty"); } else { mainContainer.RemoveFromClassList("empty"); } m_HeaderIcon.image = GetIconForVFXType(controller.model.inputType); m_HeaderIcon.SendToBack(); // Actually move it as first child so it's before the title label if (m_HeaderIcon.image == null) m_HeaderIcon.AddToClassList("Empty"); var subTitle = controller.subtitle; m_Subtitle.text = controller.subtitle; if (string.IsNullOrEmpty(subTitle)) { m_Subtitle.AddToClassList("empty"); } else { m_Subtitle.RemoveFromClassList("empty"); } Profiler.BeginSample("VFXContextUI.SetAllStyleClasses"); VFXContextType contextType = controller.model.contextType; foreach (VFXContextType value in System.Enum.GetValues(typeof(VFXContextType))) { if (value != contextType) RemoveFromClassList(ContextEnumToClassName(value.ToString())); } AddToClassList(ContextEnumToClassName(contextType.ToString())); var inputType = controller.model.inputType; if (inputType == VFXDataType.None) { inputType = controller.model.ownedType; } foreach (VFXDataType value in System.Enum.GetValues(typeof(VFXDataType))) { if (inputType != value) RemoveFromClassList("inputType" + ContextEnumToClassName(value.ToString())); } AddToClassList("inputType" + ContextEnumToClassName(inputType.ToString())); var outputType = controller.model.outputType; foreach (VFXDataType value in System.Enum.GetValues(typeof(VFXDataType))) { if (value != outputType) RemoveFromClassList("outputType" + ContextEnumToClassName(value.ToString())); } AddToClassList("outputType" + ContextEnumToClassName(outputType.ToString())); var type = controller.model.ownedType; foreach (VFXDataType value in System.Enum.GetValues(typeof(VFXDataType))) { if (value != type) RemoveFromClassList("type" + ContextEnumToClassName(value.ToString())); } AddToClassList("type" + ContextEnumToClassName(type.ToString())); var space = controller.model.space; m_HeaderSpace.text = space.ToString(); m_HeaderSpace.tooltip = $"{space.ToString()} Space"; m_HeaderSpace.RemoveFromClassList(VFXSpace.World.ToString()); m_HeaderSpace.RemoveFromClassList(VFXSpace.Local.ToString()); m_HeaderSpace.AddToClassList(space.ToString()); Profiler.EndSample(); if (controller.model.outputType == VFXDataType.None) { if (m_Footer.parent != null) m_Footer.RemoveFromHierarchy(); } else { if (m_Footer.parent == null) mainContainer.Add(m_Footer); if (controller.model.outputFlowSlot.Any()) { m_Footer.text = controller.model.outputType.ToString(); m_Footer.style.backgroundImage = GetIconForVFXType(controller.model.outputType); } else { m_Footer.text = string.Empty; m_Footer.style.backgroundImage = null; } } Profiler.BeginSample("VFXContextUI.CreateInputFlow"); HashSet newInAnchors = new HashSet(); foreach (var inanchorcontroller in controller.flowInputAnchors.Take(VFXContext.kMaxFlowCount)) { var existing = m_FlowInputConnectorContainer.Children().Select(t => t as VFXFlowAnchor).FirstOrDefault(t => t.controller == inanchorcontroller); if (existing == null) { var anchor = VFXFlowAnchor.Create(inanchorcontroller); m_FlowInputConnectorContainer.Add(anchor); newInAnchors.Add(anchor); } else { newInAnchors.Add(existing); } } foreach (var nonLongerExistingAnchor in m_FlowInputConnectorContainer.Children().Where(t => !newInAnchors.Contains(t)).ToList()) // ToList to make a copy because the enumerable will change when we delete { m_FlowInputConnectorContainer.Remove(nonLongerExistingAnchor); } Profiler.EndSample(); Profiler.BeginSample("VFXContextUI.CreateInputFlow"); HashSet newOutAnchors = new HashSet(); foreach (var outanchorcontroller in controller.flowOutputAnchors.Take(VFXContext.kMaxFlowCount)) { var existing = m_FlowOutputConnectorContainer.Children().Select(t => t as VFXFlowAnchor).FirstOrDefault(t => t.controller == outanchorcontroller); if (existing == null) { var anchor = VFXFlowAnchor.Create(outanchorcontroller); m_FlowOutputConnectorContainer.Add(anchor); newOutAnchors.Add(anchor); } else { newOutAnchors.Add(existing); } } foreach (var nonLongerExistingAnchor in m_FlowOutputConnectorContainer.Children().Where(t => !newOutAnchors.Contains(t)).ToList()) // ToList to make a copy because the enumerable will change when we delete { m_FlowOutputConnectorContainer.Remove(nonLongerExistingAnchor); } Profiler.EndSample(); UpdateLabel(); if (string.IsNullOrEmpty(m_Label.text)) { m_Label.AddToClassList("empty"); } else { m_Label.RemoveFromClassList("empty"); } foreach (var inEdge in m_FlowInputConnectorContainer.Children().OfType().SelectMany(t => t.connections)) inEdge.UpdateEdgeControl(); foreach (var outEdge in m_FlowOutputConnectorContainer.Children().OfType().SelectMany(t => t.connections)) outEdge.UpdateEdgeControl(); RefreshContext(); } public VFXContextUI() : base("uxml/VFXContext") { capabilities |= Capabilities.Selectable | Capabilities.Movable | Capabilities.Deletable | Capabilities.Ascendable; styleSheets.Add(VFXView.LoadStyleSheet("VFXContext")); styleSheets.Add(VFXView.LoadStyleSheet("Selectable")); AddToClassList("VFXContext"); AddToClassList("selectable"); this.mainContainer.style.overflow = Overflow.Visible; m_FlowInputConnectorContainer = this.Q("flow-inputs"); m_FlowOutputConnectorContainer = this.Q("flow-outputs"); m_HeaderIcon = titleContainer.Q("icon"); m_HeaderSpace = titleContainer.Q