mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-08-20 00:49:25 +08:00
456 lines
18 KiB
C#
456 lines
18 KiB
C#
|
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;
|
|||
|
/// <summary>
|
|||
|
/// Get the Node to which the current Option belongs
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
/// <summary>
|
|||
|
/// Get whether the current Option can only be connected once
|
|||
|
/// </summary>
|
|||
|
public bool IsSingle {
|
|||
|
get { return _IsSingle; }
|
|||
|
}
|
|||
|
|
|||
|
private bool _IsInput;
|
|||
|
/// <summary>
|
|||
|
/// Get whether the current Option is an input option
|
|||
|
/// </summary>
|
|||
|
public bool IsInput {
|
|||
|
get { return _IsInput; }
|
|||
|
internal set { _IsInput = value; }
|
|||
|
}
|
|||
|
|
|||
|
private Color _TextColor = Color.White;
|
|||
|
/// <summary>
|
|||
|
/// Gets or sets the current Option text color
|
|||
|
/// </summary>
|
|||
|
public Color TextColor {
|
|||
|
get { return _TextColor; }
|
|||
|
internal set {
|
|||
|
if (value == _TextColor) return;
|
|||
|
_TextColor = value;
|
|||
|
this.Invalidate();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private Color _DotColor = Color.Transparent;
|
|||
|
/// <summary>
|
|||
|
/// Gets or sets the color of the current Option connection point
|
|||
|
/// </summary>
|
|||
|
public Color DotColor {
|
|||
|
get { return _DotColor; }
|
|||
|
internal set {
|
|||
|
if (value == _DotColor) return;
|
|||
|
_DotColor = value;
|
|||
|
this.Invalidate();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private string _Text;
|
|||
|
/// <summary>
|
|||
|
/// Gets or sets the current Option display text
|
|||
|
/// This property cannot be modified when AutoSize is set
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
/// <summary>
|
|||
|
/// Get the left coordinate of the current Option connection point
|
|||
|
/// </summary>
|
|||
|
public int DotLeft {
|
|||
|
get { return _DotLeft; }
|
|||
|
internal set { _DotLeft = value; }
|
|||
|
}
|
|||
|
private int _DotTop;
|
|||
|
/// <summary>
|
|||
|
/// Get the upper coordinate of the current Option connection point
|
|||
|
/// </summary>
|
|||
|
public int DotTop {
|
|||
|
get { return _DotTop; }
|
|||
|
internal set { _DotTop = value; }
|
|||
|
}
|
|||
|
|
|||
|
private int _DotSize;
|
|||
|
/// <summary>
|
|||
|
/// Get the width of the current Option connection point
|
|||
|
/// </summary>
|
|||
|
public int DotSize {
|
|||
|
get { return _DotSize; }
|
|||
|
protected set { _DotSize = value; }
|
|||
|
}
|
|||
|
|
|||
|
private Rectangle _TextRectangle;
|
|||
|
/// <summary>
|
|||
|
/// Get the current Option text area
|
|||
|
/// </summary>
|
|||
|
public Rectangle TextRectangle {
|
|||
|
get { return _TextRectangle; }
|
|||
|
internal set { _TextRectangle = value; }
|
|||
|
}
|
|||
|
|
|||
|
private object _Data;
|
|||
|
/// <summary>
|
|||
|
/// Get or set the data contained in the current Option
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
/// <summary>
|
|||
|
/// Get the current Option data type
|
|||
|
/// </summary>
|
|||
|
public Type DataType {
|
|||
|
get { return _DataType; }
|
|||
|
internal set { _DataType = value; }
|
|||
|
}
|
|||
|
|
|||
|
//private Rectangle _DotRectangle;
|
|||
|
/// <summary>
|
|||
|
/// Get the area of the current Option connection point
|
|||
|
/// </summary>
|
|||
|
public Rectangle DotRectangle {
|
|||
|
get {
|
|||
|
return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize);
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Get the current number of Option connected
|
|||
|
/// </summary>
|
|||
|
public int ConnectionCount {
|
|||
|
get { return m_hs_connected.Count; }
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Get the Option collection that the current Option is connected to
|
|||
|
/// </summary>
|
|||
|
internal HashSet<STNodeOption> ConnectedOption {
|
|||
|
get { return m_hs_connected; }
|
|||
|
}
|
|||
|
|
|||
|
#endregion Properties
|
|||
|
/// <summary>
|
|||
|
/// Save the points that have been connected
|
|||
|
/// </summary>
|
|||
|
protected HashSet<STNodeOption> m_hs_connected;
|
|||
|
|
|||
|
#region Constructor
|
|||
|
|
|||
|
private STNodeOption() { }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Constructs an Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="strText">Display text</param>
|
|||
|
/// <param name="dataType">Data Type</param>
|
|||
|
/// <param name="bSingle">Whether it is a single connection</param>
|
|||
|
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<STNodeOption>();
|
|||
|
this._DataType = dataType;
|
|||
|
this._Text = strText;
|
|||
|
this._IsSingle = bSingle;
|
|||
|
}
|
|||
|
|
|||
|
#endregion Builder
|
|||
|
|
|||
|
#region Event
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Occurs when connected
|
|||
|
/// </summary>
|
|||
|
public event STNodeOptionEventHandler Connected;
|
|||
|
/// <summary>
|
|||
|
/// Occurs when a connection starts happening
|
|||
|
/// </summary>
|
|||
|
public event STNodeOptionEventHandler Connecting;
|
|||
|
/// <summary>
|
|||
|
/// Occurs when the connection is disconnected
|
|||
|
/// </summary>
|
|||
|
public event STNodeOptionEventHandler DisConnected;
|
|||
|
/// <summary>
|
|||
|
/// Occurs when the connection starts to drop
|
|||
|
/// </summary>
|
|||
|
public event STNodeOptionEventHandler DisConnecting;
|
|||
|
/// <summary>
|
|||
|
/// Occurs when data is passed
|
|||
|
/// </summary>
|
|||
|
public event STNodeOptionEventHandler DataTransfer;
|
|||
|
|
|||
|
#endregion Event
|
|||
|
|
|||
|
#region protected
|
|||
|
/// <summary>
|
|||
|
/// Redraw the entire control
|
|||
|
/// </summary>
|
|||
|
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);
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// The current Option starts to connect to the target Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="op">Option to connect</param>
|
|||
|
/// <returns>Are you allowed to continue the operation?</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// The current Option starts to disconnect the target Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="op">Option to be disconnected</param>
|
|||
|
/// <returns>Are you allowed to continue the operation?</returns>
|
|||
|
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
|
|||
|
/// <summary>
|
|||
|
/// The current Option is connected to the target Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="op">Option to connect</param>
|
|||
|
/// <returns>Connection result</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Check whether the current Option can connect to the target Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="op">Option to connect</param>
|
|||
|
/// <returns>Test results</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// The current Option disconnects the target Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="op">Option to be disconnected</param>
|
|||
|
/// <returns></returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Disconnect all connections of the current Option
|
|||
|
/// </summary>
|
|||
|
public void DisConnectionAll() {
|
|||
|
if (this._DataType == null) return;
|
|||
|
var arr = m_hs_connected.ToArray();
|
|||
|
foreach (var v in arr) {
|
|||
|
this.DisConnectOption(v);
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Get the Option collection that the current Option is connected to
|
|||
|
/// </summary>
|
|||
|
/// <returns>If it is null, it means that there is no owner, otherwise it returns the collection</returns>
|
|||
|
public List<STNodeOption> GetConnectedOption() {
|
|||
|
if (this._DataType == null) return null;
|
|||
|
if (!this._IsInput)
|
|||
|
return m_hs_connected.ToList();
|
|||
|
List<STNodeOption> lst = new List<STNodeOption>();
|
|||
|
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;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Post data to all Option connected to the current Option
|
|||
|
/// </summary>
|
|||
|
public void TransferData() {
|
|||
|
if (this._DataType == null) return;
|
|||
|
foreach (var v in m_hs_connected) {
|
|||
|
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Post data to all Option connected to the current Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="data">Data to be delivered</param>
|
|||
|
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));
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Post data to all Option connected to the current Option
|
|||
|
/// </summary>
|
|||
|
/// <param name="data">Data to be delivered</param>
|
|||
|
/// <param name="bDisposeOld">Whether to release old data</param>
|
|||
|
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
|
|||
|
}
|
|||
|
}
|