using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace ST.Library.UI.NodeEditor { public class STNodeOption { #region Properties public static readonly STNodeOption Empty = new STNodeOption(); private STNode _Owner; /// /// Get the Node to which the current Option belongs /// public STNode Owner { get { return _Owner; } internal set { if (value == _Owner) return; if (_Owner != null) this.DisConnectionAll(); //Disconnect all current connections when the owner changes _Owner = value; } } private bool _IsSingle; /// /// Get whether the current Option can only be connected once /// public bool IsSingle { get { return _IsSingle; } } private bool _IsInput; /// /// Get whether the current Option is an input option /// public bool IsInput { get { return _IsInput; } internal set { _IsInput = value; } } private Color _TextColor = Color.White; /// /// Gets or sets the current Option text color /// public Color TextColor { get { return _TextColor; } internal set { if (value == _TextColor) return; _TextColor = value; this.Invalidate(); } } private Color _DotColor = Color.Transparent; /// /// Gets or sets the color of the current Option connection point /// public Color DotColor { get { return _DotColor; } internal set { if (value == _DotColor) return; _DotColor = value; this.Invalidate(); } } private string _Text; /// /// Gets or sets the current Option display text /// This property cannot be modified when AutoSize is set /// public string Text { get { return _Text; } internal set { if (value == _Text) return; _Text = value; if (this._Owner == null) return; this._Owner.BuildSize(true, true, true); } } private int _DotLeft; /// /// Get the left coordinate of the current Option connection point /// public int DotLeft { get { return _DotLeft; } internal set { _DotLeft = value; } } private int _DotTop; /// /// Get the upper coordinate of the current Option connection point /// public int DotTop { get { return _DotTop; } internal set { _DotTop = value; } } private int _DotSize; /// /// Get the width of the current Option connection point /// public int DotSize { get { return _DotSize; } protected set { _DotSize = value; } } private Rectangle _TextRectangle; /// /// Get the current Option text area /// public Rectangle TextRectangle { get { return _TextRectangle; } internal set { _TextRectangle = value; } } private object _Data; /// /// Get or set the data contained in the current Option /// public object Data { get { return _Data; } set { if (value != null) { if (this._DataType == null) return; var t = value.GetType(); if (t != this._DataType && !t.IsSubclassOf(this._DataType)) { throw new ArgumentException("Invalid data type The data type must be the specified data type or its subclass"); } } _Data = value; } } private Type _DataType; /// /// Get the current Option data type /// public Type DataType { get { return _DataType; } internal set { _DataType = value; } } //private Rectangle _DotRectangle; /// /// Get the area of ​​the current Option connection point /// public Rectangle DotRectangle { get { return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize); } } /// /// Get the current number of Option connected /// public int ConnectionCount { get { return m_hs_connected.Count; } } /// /// Get the Option collection that the current Option is connected to /// internal HashSet ConnectedOption { get { return m_hs_connected; } } #endregion Properties /// /// Save the points that have been connected /// protected HashSet m_hs_connected; #region Constructor private STNodeOption() { } /// /// Constructs an Option /// /// Display text /// Data Type /// Whether it is a single connection public STNodeOption(string strText, Type dataType, bool bSingle) { if (dataType == null) throw new ArgumentNullException("The specified data type cannot be null"); this._DotSize = 10; m_hs_connected = new HashSet(); this._DataType = dataType; this._Text = strText; this._IsSingle = bSingle; } #endregion Builder #region Event /// /// Occurs when connected /// public event STNodeOptionEventHandler Connected; /// /// Occurs when a connection starts happening /// public event STNodeOptionEventHandler Connecting; /// /// Occurs when the connection is disconnected /// public event STNodeOptionEventHandler DisConnected; /// /// Occurs when the connection starts to drop /// public event STNodeOptionEventHandler DisConnecting; /// /// Occurs when data is passed /// public event STNodeOptionEventHandler DataTransfer; #endregion Event #region protected /// /// Redraw the entire control /// protected void Invalidate() { if (this._Owner == null) return; this._Owner.Invalidate(); } /* * At first I thought that only input type options should have events because input is passive and output is active * But later found that events are used for output nodes in STNodeHub, for example * Just in case, it is not very problematic to comment the code here. The output option does not register the event, and the same effect */ protected internal virtual void OnConnected(STNodeOptionEventArgs e) { if (this.Connected != null/* && this._IsInput*/) this.Connected(this, e); } protected internal virtual void OnConnecting(STNodeOptionEventArgs e) { if (this.Connecting != null) this.Connecting(this, e); } protected internal virtual void OnDisConnected(STNodeOptionEventArgs e) { if (this.DisConnected != null/* && this._IsInput*/) this.DisConnected(this, e); } protected internal virtual void OnDisConnecting(STNodeOptionEventArgs e) { if (this.DisConnecting != null) this.DisConnecting(this, e); } protected internal virtual void OnDataTransfer(STNodeOptionEventArgs e) { if (this.DataTransfer != null/* && this._IsInput*/) this.DataTransfer(this, e); } protected void STNodeEidtorConnected(STNodeEditorOptionEventArgs e) { if (this._Owner == null) return; if (this._Owner.Owner == null) return; this._Owner.Owner.OnOptionConnected(e); } protected void STNodeEidtorDisConnected(STNodeEditorOptionEventArgs e) { if (this._Owner == null) return; if (this._Owner.Owner == null) return; this._Owner.Owner.OnOptionDisConnected(e); } /// /// The current Option starts to connect to the target Option /// /// Option to connect /// Are you allowed to continue the operation? protected virtual bool ConnectingOption(STNodeOption op) { if (this._Owner == null) return false; if (this._Owner.Owner == null) return false; STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Connecting); this._Owner.Owner.OnOptionConnecting(e); this.OnConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.Connecting)); op.OnConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.Connecting)); return e.Continue; } /// /// The current Option starts to disconnect the target Option /// /// Option to be disconnected /// Are you allowed to continue the operation? protected virtual bool DisConnectingOption(STNodeOption op) { if (this._Owner == null) return false; if (this._Owner.Owner == null) return false; STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnecting); this._Owner.Owner.OnOptionDisConnecting(e); this.OnDisConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.DisConnecting)); op.OnDisConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.DisConnecting)); return e.Continue; } #endregion protected #region public /// /// The current Option is connected to the target Option /// /// Option to connect /// Connection result public virtual ConnectionStatus ConnectOption(STNodeOption op) { if (!this.ConnectingOption(op)) { this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject)); return ConnectionStatus.Reject; } var v = this.CanConnect(op); if (v != ConnectionStatus.Connected) { this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v)); return v; } v = op.CanConnect(this); if (v != ConnectionStatus.Connected) { this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v)); return v; } op.AddConnection(this, false); this.AddConnection(op, true); this.ControlBuildLinePath(); this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v)); return v; } /// /// Check whether the current Option can connect to the target Option /// /// Option to connect /// Test results public virtual ConnectionStatus CanConnect(STNodeOption op) { if (this == STNodeOption.Empty || op == STNodeOption.Empty) return ConnectionStatus.EmptyOption; if (this._IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput; if (op.Owner == null || this._Owner == null) return ConnectionStatus.NoOwner; if (op.Owner == this._Owner) return ConnectionStatus.SameOwner; if (this._Owner.LockOption || op._Owner.LockOption) return ConnectionStatus.Locked; if (this._IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption; if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this._Owner)) return ConnectionStatus.Loop; if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists; if (this._IsInput && op._DataType != this._DataType && !op._DataType.IsSubclassOf(this._DataType)) return ConnectionStatus.ErrorType; return ConnectionStatus.Connected; } /// /// The current Option disconnects the target Option /// /// Option to be disconnected /// public virtual ConnectionStatus DisConnectOption(STNodeOption op) { if (!this.DisConnectingOption(op)) { this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject)); return ConnectionStatus.Reject; } if (op.Owner == null) return ConnectionStatus.NoOwner; if (this._Owner == null) return ConnectionStatus.NoOwner; if (op.Owner.LockOption && this._Owner.LockOption) { this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Locked)); return ConnectionStatus.Locked; } op.RemoveConnection(this, false); this.RemoveConnection(op, true); this.ControlBuildLinePath(); this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnected)); return ConnectionStatus.DisConnected; } /// /// Disconnect all connections of the current Option /// public void DisConnectionAll() { if (this._DataType == null) return; var arr = m_hs_connected.ToArray(); foreach (var v in arr) { this.DisConnectOption(v); } } /// /// Get the Option collection that the current Option is connected to /// /// If it is null, it means that there is no owner, otherwise it returns the collection public List GetConnectedOption() { if (this._DataType == null) return null; if (!this._IsInput) return m_hs_connected.ToList(); List lst = new List(); if (this._Owner == null) return null; if (this._Owner.Owner == null) return null; foreach (var v in this._Owner.Owner.GetConnectionInfo()) { if (v.Output == this) lst.Add(v.Input); } return lst; } /// /// Post data to all Option connected to the current Option /// public void TransferData() { if (this._DataType == null) return; foreach (var v in m_hs_connected) { v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected)); } } /// /// Post data to all Option connected to the current Option /// /// Data to be delivered public void TransferData(object data) { if (this._DataType == null) return; this.Data = data; //not this._Data foreach (var v in m_hs_connected) { v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected)); } } /// /// Post data to all Option connected to the current Option /// /// Data to be delivered /// Whether to release old data public void TransferData(object data, bool bDisposeOld) { if (bDisposeOld && this._Data != null) { if (this._Data is IDisposable) ((IDisposable)this._Data).Dispose(); this._Data = null; } this.TransferData(data); } #endregion public #region internal private bool AddConnection(STNodeOption op, bool bSponsor) { if (this._DataType == null) return false; bool b = m_hs_connected.Add(op); this.OnConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected)); if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected)); return b; } private bool RemoveConnection(STNodeOption op, bool bSponsor) { if (this._DataType == null) return false; bool b = false; if (m_hs_connected.Contains(op)) { b = m_hs_connected.Remove(op); if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.DisConnected)); this.OnDisConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected)); } return b; } #endregion internal #region private private void ControlBuildLinePath() { if (this.Owner == null) return; if (this.Owner.Owner == null) return; this.Owner.Owner.BuildLinePath(); } #endregion } }