// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System; using System.Collections.Generic; using System.Linq; using Doozy.Editor.EditorUI; using Doozy.Editor.EditorUI.Components; using Doozy.Editor.EditorUI.ScriptableObjects.Colors; using Doozy.Editor.EditorUI.Utils; using Doozy.Editor.Reactor.Internal; using Doozy.Runtime.Common.Extensions; using Doozy.Runtime.Nody; using Doozy.Runtime.Reactor; using Doozy.Runtime.Reactor.Easings; using Doozy.Runtime.Reactor.Extensions; using Doozy.Runtime.Reactor.Internal; using Doozy.Runtime.Reactor.Reactions; using Doozy.Runtime.UIElements.Extensions; using UnityEditor; using UnityEngine; using UnityEditor.Experimental.GraphView; using UnityEngine.UIElements; using EditorStyles = Doozy.Editor.EditorUI.EditorStyles; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable CollectionNeverQueried.Global // ReSharper disable MemberCanBeProtected.Global // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable VirtualMemberCallInConstructor // ReSharper disable VirtualMemberNeverOverridden.Global // ReSharper disable UnusedMember.Global // ReSharper disable ForCanBeConvertedToForeach namespace Doozy.Editor.Nody { public partial class FlowNodeView : Node, IDisposable { public virtual void Dispose() { addOutputButton?.Recycle(); activeStateLineIndicatorReaction?.Recycle(); deleteLockIconReaction?.Recycle(); nodeIconReaction?.Recycle(); runningStateIconIndicatorReaction?.Recycle(); activeStateBorderReaction?.Recycle(); pingBorderReaction?.Recycle(); flowNode.ping -= Ping; flowNode.refreshNodeView -= RefreshNodeView; flowNode.refreshNodeView -= RefreshPortsViews; } public virtual Type nodeType => null; public virtual Texture2D nodeIconTexture => EditorTextures.Nody.Icons.Infinity; public virtual IEnumerable nodeIconTextures => EditorSpriteSheets.Nody.Icons.Nody; public virtual Color nodeAccentColor => EditorColors.Nody.Color; public virtual EditorSelectableColorInfo nodeSelectableAccentColor => EditorSelectableColors.Nody.Color; public FlowGraphView graphView { get; } public FlowNode flowNode { get; } public Action OnNodeSelected; public List inputPortViews { get; set; } public List outputPortViews { get; set; } protected FluidButton addOutputButton { get; set; } private static string s_uxmlPath; private static string uxmlPath => !s_uxmlPath.IsNullOrEmpty() ? s_uxmlPath : s_uxmlPath = AssetDatabase.GetAssetPath(EditorLayouts.Nody.NodeView); public ColorReaction activeStateBorderReaction { get; internal set; } public ColorReaction pingBorderReaction { get; internal set; } public Image activeStateLineIndicator { get; private set; } public Image deleteLockIcon { get; private set; } public Image nodeIcon { get; private set; } public Image runningStateIconIndicator { get; private set; } public Label nodeDescriptionLabel { get; private set; } public Label nodeTitleLabel { get; private set; } public Texture2DReaction activeStateLineIndicatorReaction { get; internal set; } public Texture2DReaction deleteLockIconReaction { get; private set; } public Texture2DReaction nodeIconReaction { get; internal set; } public Texture2DReaction runningStateIconIndicatorReaction { get; internal set; } public VisualElement activeStateBorder { get; private set; } public VisualElement nodeBorder { get; private set; } public VisualElement nodeContent { get; private set; } public VisualElement nodeInfo { get; private set; } public VisualElement nodeSelectionBorder { get; private set; } public VisualElement pingBorder { get; private set; } public VisualElement portDivider { get; private set; } public VisualElement top { get; private set; } public Color stateColor { get { switch (flowNode.nodeState) { case NodeState.Idle: return EditorColors.Nody.StateIdle; case NodeState.Running: return EditorColors.Nody.StateRunning; case NodeState.Active: return EditorColors.Nody.StateActive; default: throw new ArgumentOutOfRangeException(); } } } public SerializedObject serializedObject { get; } public SerializedProperty propertyFlowGraphId { get; private set; } public SerializedProperty propertyNodeId { get; private set; } public SerializedProperty propertyNodeType { get; private set; } public SerializedProperty propertyNodeName { get; private set; } public SerializedProperty propertyDescription { get; private set; } public SerializedProperty propertyInputPorts { get; private set; } public SerializedProperty propertyOutputPorts { get; private set; } public SerializedProperty propertyNodeState { get; private set; } public SerializedProperty propertyPosition { get; private set; } public SerializedProperty propertyPassthrough { get; private set; } public SerializedProperty propertyClearGraphHistory { get; private set; } protected FlowNodeView(FlowGraphView graphView, FlowNode node) : base(uxmlPath) { this.flowNode = node; this.graphView = graphView; serializedObject = new SerializedObject(node); FindProperties(); node.ping += Ping; node.refreshNodeView += RefreshNodeView; node.refreshNodeView += RefreshPortsViews; // RegisterCallback(evt => nodeIconReaction?.Play()); RegisterCallback(_ => nodeIconReaction?.Play()); inputPortViews ??= new List(); outputPortViews ??= new List(); InitializeView(); RefreshNodeView(); RefreshPortsViews(); } protected virtual void FindProperties() { propertyFlowGraphId = serializedObject.FindProperty("FlowGraphId"); propertyNodeId = serializedObject.FindProperty("NodeId"); propertyNodeType = serializedObject.FindProperty("NodeType"); propertyNodeName = serializedObject.FindProperty("NodeName"); propertyDescription = serializedObject.FindProperty("NodeDescription"); propertyInputPorts = serializedObject.FindProperty("InputPorts"); propertyOutputPorts = serializedObject.FindProperty("OutputPorts"); propertyNodeState = serializedObject.FindProperty("NodeState"); propertyPosition = serializedObject.FindProperty("Position"); propertyPassthrough = serializedObject.FindProperty("Passthrough"); propertyClearGraphHistory = serializedObject.FindProperty("ClearGraphHistory"); } #region Ping public void Ping(FlowDirection direction) { Color directionColor; switch (direction) { case FlowDirection.Forward: directionColor = EditorColors.Nody.StateActive; break; case FlowDirection.Back: directionColor = EditorColors.Nody.BackFlow; break; default: throw new ArgumentOutOfRangeException(nameof(direction), direction, null); } pingBorderReaction.Stop(); pingBorderReaction.SetFrom(directionColor); pingBorderReaction.SetTo(Color.clear); if (direction == FlowDirection.Back) pingBorderReaction.Play(); } public void Ping(Color color) { pingBorderReaction.Stop(); pingBorderReaction.SetFrom(color); pingBorderReaction.SetTo(Color.clear); pingBorderReaction.Play(); } #endregion /// Refresh the node (call when node's data changes) public virtual void RefreshNodeView() { serializedObject.UpdateIfRequiredOrScript(); viewDataKey = flowNode.nodeId; title = flowNode.name; nodeTitleLabel.SetText(flowNode.nodeName); nodeDescriptionLabel.SetText(flowNode.nodeDescription); nodeDescriptionLabel.SetStyleDisplay(flowNode.nodeDescription.IsNullOrEmpty() ? DisplayStyle.None : DisplayStyle.Flex); style.left = flowNode.position.x; style.top = flowNode.position.y; // UpdateNodeState(node.nodeState); } public virtual void RefreshPortsViews() { // Remove all input ports for (int i = inputPortViews.Count - 1; i >= 0; i--) { FlowPortView p = inputPortViews[i]; inputPortViews.RemoveAt(i); p?.Dispose(); } if (inputPortViews.Count > 0) inputPortViews.Clear(); // Add all input ports for (int i = 0; i < flowNode.inputPorts.Count; i++) { FlowPort port = flowNode.inputPorts[i]; port.refreshPortView -= RefreshData; port.refreshPortView += RefreshData; AddPortView(port); } // Remove all output ports for (int i = outputPortViews.Count - 1; i >= 0; i--) { FlowPortView p = outputPortViews[i]; outputPortViews.RemoveAt(i); p?.Dispose(); } if (outputPortViews.Count > 0) outputPortViews.Clear(); // Add all output ports for (int i = 0; i < flowNode.outputPorts.Count; i++) { FlowPort port = flowNode.outputPorts[i]; port.refreshPortView -= RefreshData; port.refreshPortView += RefreshData; AddPortView(port); } graphView.RefreshEdges(); RefreshPorts(); } public virtual void RefreshData() { } /// Called every time the node state changes its state /// New node state protected virtual void OnNodeStateChanged(NodeState newState) => UpdateNodeState(newState); /// Update the visual state of the node according to the new node state /// New node state public void UpdateNodeState(NodeState newState = NodeState.Idle) { switch (newState) { case NodeState.Idle: runningStateIconIndicatorReaction.SetFirstFrame(); activeStateBorderReaction.SetProgressAtOne(); activeStateBorderReaction.Play(PlayDirection.Reverse); activeStateLineIndicatorReaction.SetFirstFrame(); break; case NodeState.Running: runningStateIconIndicatorReaction.Play(); if (flowNode.nodeType == NodeType.Global) { activeStateBorderReaction.SetProgressAtOne(); activeStateBorderReaction.Play(PlayDirection.Reverse); activeStateLineIndicatorReaction.SetFirstFrame(); } break; case NodeState.Active: activeStateBorderReaction.Play(PlayDirection.Forward); activeStateLineIndicatorReaction.Play(); nodeIconReaction?.Play(); break; default: throw new ArgumentOutOfRangeException(nameof(newState), newState, null); } } protected virtual void InitializeView() { this .AddStyle(EditorStyles.Nody.NodeView) .SetStyleMargins(0) .SetStylePadding(0); nodeBorder = this.Q("node-border"); nodeSelectionBorder = this.Q("selection-border"); nodeIcon = this.Q(nameof(nodeIcon)); nodeTitleLabel = this.Q