From 7eaf98400550e04cde63d929d3cb0cecbcbcaed6 Mon Sep 17 00:00:00 2001 From: dexy Date: Fri, 28 Jan 2022 04:16:19 +1100 Subject: [PATCH] Added STNodeEditor and translated to english --- .../STNodeEditor/FrmNodePreviewPanel.cs | 144 ++ .../STNodeEditor/FrmSTNodePropertyInput.cs | 114 + .../STNodeEditor/FrmSTNodePropertySelect.cs | 116 + CodeWalker.WinForms/STNodeEditor/STNode.cs | 1117 +++++++++ .../STNodeEditor/STNodeAttribute.cs | 121 + .../STNodeEditor/STNodeCollection.cs | 253 ++ .../STNodeEditor/STNodeControl.cs | 319 +++ .../STNodeEditor/STNodeControlCollection.cs | 214 ++ .../STNodeEditor/STNodeEditor.cs | 2109 +++++++++++++++++ .../STNodeEditor/STNodeEditorDataType.cs | 203 ++ .../STNodeEditor/STNodeEditorPanel.cs | 329 +++ CodeWalker.WinForms/STNodeEditor/STNodeHub.cs | 192 ++ .../STNodeEditor/STNodeOption.cs | 456 ++++ .../STNodeEditor/STNodeOptionCollection.cs | 238 ++ .../STNodeEditor/STNodePropertyAttribute.cs | 334 +++ .../STNodeEditor/STNodePropertyGrid.cs | 860 +++++++ .../STNodeEditor/STNodeTreeView.cs | 907 +++++++ 17 files changed, 8026 insertions(+) create mode 100644 CodeWalker.WinForms/STNodeEditor/FrmNodePreviewPanel.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertyInput.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertySelect.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNode.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeAttribute.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeCollection.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeControl.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeControlCollection.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeEditor.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeEditorDataType.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeEditorPanel.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeHub.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeOption.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeOptionCollection.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodePropertyAttribute.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodePropertyGrid.cs create mode 100644 CodeWalker.WinForms/STNodeEditor/STNodeTreeView.cs diff --git a/CodeWalker.WinForms/STNodeEditor/FrmNodePreviewPanel.cs b/CodeWalker.WinForms/STNodeEditor/FrmNodePreviewPanel.cs new file mode 100644 index 0000000..fa8b517 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/FrmNodePreviewPanel.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.Runtime.InteropServices; + +namespace ST.Library.UI.NodeEditor +{ + internal class FrmNodePreviewPanel : Form + { + public Color BorderColor { get; set; } + public bool AutoBorderColor { get; set; } + + private bool m_bRight; + private Point m_ptHandle; + private int m_nHandleSize; + private Rectangle m_rect_handle; + private Rectangle m_rect_panel; + private Rectangle m_rect_exclude; + private Region m_region; + private Type m_type; + private STNode m_node; + private STNodeEditor m_editor; + private STNodePropertyGrid m_property; + + private Pen m_pen = new Pen(Color.Black); + private SolidBrush m_brush = new SolidBrush(Color.Black); + private static FrmNodePreviewPanel m_last_frm; + + [DllImport("user32.dll")] + private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); + + public FrmNodePreviewPanel(Type stNodeType, Point ptHandle, int nHandleSize, bool bRight, STNodeEditor editor, STNodePropertyGrid propertyGrid) { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + if (m_last_frm != null) m_last_frm.Close(); + m_last_frm = this; + + m_editor = editor; + m_property = propertyGrid; + m_editor.Size = new Size(200, 200); + m_property.Size = new Size(200, 200); + m_editor.Location = new Point(1 + (bRight ? nHandleSize : 0), 1); + m_property.Location = new Point(m_editor.Right, 1); + m_property.InfoFirstOnDraw = true; + this.Controls.Add(m_editor); + this.Controls.Add(m_property); + this.ShowInTaskbar = false; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Size = new Size(402 + nHandleSize, 202); + + m_type = stNodeType; + m_ptHandle = ptHandle; + m_nHandleSize = nHandleSize; + m_bRight = bRight; + + this.AutoBorderColor = true; + this.BorderColor = Color.DodgerBlue; + } + + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + m_node = (STNode)Activator.CreateInstance(m_type); + m_node.Left = 20; m_node.Top = 20; + m_editor.Nodes.Add(m_node); + m_property.SetNode(m_node); + + m_rect_panel = new Rectangle(0, 0, 402, 202); + m_rect_handle = new Rectangle(m_ptHandle.X, m_ptHandle.Y, m_nHandleSize, m_nHandleSize); + m_rect_exclude = new Rectangle(0, m_nHandleSize, m_nHandleSize, this.Height - m_nHandleSize); + if (m_bRight) { + this.Left = m_ptHandle.X; + m_rect_panel.X = m_ptHandle.X + m_nHandleSize; + } else { + this.Left = m_ptHandle.X - this.Width + m_nHandleSize; + m_rect_exclude.X = this.Width - m_nHandleSize; + m_rect_panel.X = this.Left; + } + if (m_ptHandle.Y + this.Height > Screen.GetWorkingArea(this).Bottom) { + this.Top = m_ptHandle.Y - this.Height + m_nHandleSize; + m_rect_exclude.Y -= m_nHandleSize; + } else this.Top = m_ptHandle.Y; + m_rect_panel.Y = this.Top; + m_region = new Region(new Rectangle(Point.Empty, this.Size)); + m_region.Exclude(m_rect_exclude); + using (Graphics g = this.CreateGraphics()) { + IntPtr h = m_region.GetHrgn(g); + FrmNodePreviewPanel.SetWindowRgn(this.Handle, h, false); + m_region.ReleaseHrgn(h); + } + + this.MouseLeave += Event_MouseLeave; + m_editor.MouseLeave += Event_MouseLeave; + m_property.MouseLeave += Event_MouseLeave; + this.BeginInvoke(new MethodInvoker(() => { + m_property.Focus(); + })); + } + + protected override void OnClosing(CancelEventArgs e) { + base.OnClosing(e); + this.Controls.Clear(); + m_editor.Nodes.Clear(); + m_editor.MouseLeave -= Event_MouseLeave; + m_property.MouseLeave -= Event_MouseLeave; + m_last_frm = null; + } + + void Event_MouseLeave(object sender, EventArgs e) { + Point pt = Control.MousePosition; + if (m_rect_panel.Contains(pt) || m_rect_handle.Contains(pt)) return; + this.Close(); + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + m_pen.Color = this.AutoBorderColor ? m_node.TitleColor : this.BorderColor; + m_brush.Color = m_pen.Color; + g.DrawRectangle(m_pen, 0, 0, this.Width - 1, this.Height - 1); + g.FillRectangle(m_brush, m_rect_exclude.X - 1, m_rect_exclude.Y - 1, m_rect_exclude.Width + 2, m_rect_exclude.Height + 2); + + Rectangle rect = this.RectangleToClient(m_rect_handle); + rect.Y = (m_nHandleSize - 14) / 2; + rect.X += rect.Y + 1; + rect.Width = rect.Height = 14; + m_pen.Width = 2; + g.DrawLine(m_pen, rect.X + 4, rect.Y + 3, rect.X + 10, rect.Y + 3); + g.DrawLine(m_pen, rect.X + 4, rect.Y + 6, rect.X + 10, rect.Y + 6); + g.DrawLine(m_pen, rect.X + 4, rect.Y + 11, rect.X + 10, rect.Y + 11); + g.DrawLine(m_pen, rect.X + 7, rect.Y + 7, rect.X + 7, rect.Y + 10); + m_pen.Width = 1; + g.DrawRectangle(m_pen, rect.X, rect.Y, rect.Width - 1, rect.Height - 1); + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertyInput.cs b/CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertyInput.cs new file mode 100644 index 0000000..a064a76 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertyInput.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Windows.Forms; +using System.Drawing; +using ST.Library.UI.NodeEditor; + +namespace ST.Library.UI +{ + internal class FrmSTNodePropertyInput : Form + { + private STNodePropertyDescriptor m_descriptor; + private Rectangle m_rect; + private Pen m_pen; + private SolidBrush m_brush; + private TextBox m_tbx; + + public FrmSTNodePropertyInput(STNodePropertyDescriptor descriptor) { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + m_rect = descriptor.RectangleR; + m_descriptor = descriptor; + this.ShowInTaskbar = false; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.BackColor = descriptor.Control.AutoColor ? descriptor.Node.TitleColor : descriptor.Control.ItemSelectedColor; + m_pen = new Pen(descriptor.Control.ForeColor, 1); + m_brush = new SolidBrush(this.BackColor); + } + + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + Point pt = m_descriptor.Control.PointToScreen(m_rect.Location); + pt.Y += m_descriptor.Control.ScrollOffset; + this.Location = pt; + this.Size = new System.Drawing.Size(m_rect.Width + m_rect.Height, m_rect.Height); + + m_tbx = new TextBox(); + m_tbx.Font = m_descriptor.Control.Font; + m_tbx.ForeColor = m_descriptor.Control.ForeColor; + m_tbx.BackColor = Color.FromArgb(255, m_descriptor.Control.ItemValueBackColor); + m_tbx.BorderStyle = BorderStyle.None; + + m_tbx.Size = new Size(this.Width - 4 - m_rect.Height, this.Height - 2); + m_tbx.Text = m_descriptor.GetStringFromValue(); + this.Controls.Add(m_tbx); + m_tbx.Location = new Point(2, (this.Height - m_tbx.Height) / 2); + m_tbx.SelectAll(); + m_tbx.LostFocus += (s, ea) => this.Close(); + m_tbx.KeyDown += new KeyEventHandler(tbx_KeyDown); + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + m_brush.Color = m_tbx.BackColor; + g.FillRectangle(m_brush, 1, 1, this.Width - 2 - m_rect.Height, this.Height - 2); + m_brush.Color = m_descriptor.Control.ForeColor; + //Enter + g.FillPolygon(m_brush, new Point[]{ + new Point(this.Width - 21, this.Height - 2), + new Point(this.Width - 14, this.Height - 2), + new Point(this.Width - 14, this.Height - 8) + }); + g.DrawLine(m_pen, this.Width - 14, this.Height - 3, this.Width - 4, this.Height - 3); + g.DrawLine(m_pen, this.Width - 4, this.Height - 3, this.Width - 4, 14); + g.DrawLine(m_pen, this.Width - 8, 13, this.Width - 4, 13); + //---- + g.DrawLine(m_pen, this.Width - 19, 11, this.Width - 4, 11); + //E + g.DrawLine(m_pen, this.Width - 19, 3, this.Width - 16, 3); + g.DrawLine(m_pen, this.Width - 19, 6, this.Width - 16, 6); + g.DrawLine(m_pen, this.Width - 19, 9, this.Width - 16, 9); + g.DrawLine(m_pen, this.Width - 19, 3, this.Width - 19, 9); + //S + g.DrawLine(m_pen, this.Width - 13, 3, this.Width - 10, 3); + g.DrawLine(m_pen, this.Width - 13, 6, this.Width - 10, 6); + g.DrawLine(m_pen, this.Width - 13, 9, this.Width - 10, 9); + g.DrawLine(m_pen, this.Width - 13, 3, this.Width - 13, 6); + g.DrawLine(m_pen, this.Width - 10, 6, this.Width - 10, 9); + //C + g.DrawLine(m_pen, this.Width - 7, 3, this.Width - 4, 3); + g.DrawLine(m_pen, this.Width - 7, 9, this.Width - 4, 9); + g.DrawLine(m_pen, this.Width - 7, 3, this.Width - 7, 9); + } + + void tbx_KeyDown(object sender, KeyEventArgs e) { + if (e.KeyCode == Keys.Escape) this.Close(); + if (e.KeyCode != Keys.Enter) return; + try { + m_descriptor.SetValue(((TextBox)sender).Text, null); + m_descriptor.Control.Invalidate();//add rect; + } catch (Exception ex) { + m_descriptor.OnSetValueError(ex); + } + this.Close(); + } + + private void InitializeComponent() { + this.SuspendLayout(); + // + // FrmSTNodePropertyInput + // + this.ClientSize = new System.Drawing.Size(292, 273); + this.Name = "FrmSTNodePropertyInput"; + this.ResumeLayout(false); + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertySelect.cs b/CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertySelect.cs new file mode 100644 index 0000000..5bf08bb --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/FrmSTNodePropertySelect.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Windows.Forms; +using System.Drawing; + +namespace ST.Library.UI.NodeEditor +{ + internal class FrmSTNodePropertySelect : Form + { + private STNodePropertyDescriptor m_descriptor; + private int m_nItemHeight = 25; + + private static Type m_t_bool = typeof(bool); + private Pen m_pen; + private SolidBrush m_brush; + private StringFormat m_sf; + private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0);// Color.FromArgb(255, 40, 40, 40); + private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255);// Color.FromArgb(255, 50, 50, 50); + private object m_item_hover; + + public FrmSTNodePropertySelect(STNodePropertyDescriptor descriptor) { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + m_descriptor = descriptor; + this.Size = descriptor.RectangleR.Size; + this.ShowInTaskbar = false; + this.BackColor = descriptor.Control.BackColor; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + m_pen = new Pen(descriptor.Control.AutoColor ? descriptor.Node.TitleColor : descriptor.Control.ItemSelectedColor, 1); + m_brush = new SolidBrush(this.BackColor); + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + } + + private List m_lst_item = new List(); + + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + Point pt = m_descriptor.Control.PointToScreen(m_descriptor.RectangleR.Location); + pt.Y += m_descriptor.Control.ScrollOffset; + this.Location = pt; + if (m_descriptor.PropertyInfo.PropertyType.IsEnum) { + foreach (var v in Enum.GetValues(m_descriptor.PropertyInfo.PropertyType)) m_lst_item.Add(v); + } else if (m_descriptor.PropertyInfo.PropertyType == m_t_bool) { + m_lst_item.Add(true); + m_lst_item.Add(false); + } else { + this.Close(); + return; + } + this.Height = m_lst_item.Count * m_nItemHeight; + Rectangle rect = Screen.GetWorkingArea(this); + if (this.Bottom > rect.Bottom) this.Top -= (this.Bottom - rect.Bottom); + this.MouseLeave += (s, ea) => this.Close(); + this.LostFocus += (s, ea) => this.Close(); + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + Rectangle rect_back = new Rectangle(0, 0, this.Width, m_nItemHeight); + Rectangle rect_font = new Rectangle(10, 0, this.Width - 13, m_nItemHeight); + int nIndex = 0; + string strVal = m_descriptor.GetStringFromValue(); + foreach (var v in m_lst_item) { + m_brush.Color = nIndex++ % 2 == 0 ? m_clr_item_1 : m_clr_item_2; + g.FillRectangle(m_brush, rect_back); + if (v == m_item_hover) { + m_brush.Color = m_descriptor.Control.ItemHoverColor; + g.FillRectangle(m_brush, rect_back); + } + if (v.ToString() == strVal) { + m_brush.Color = m_descriptor.Control.ItemSelectedColor; + g.FillRectangle(m_brush, 4, rect_back.Top + 10, 5, 5); + } + m_brush.Color = m_descriptor.Control.ForeColor; + g.DrawString(v.ToString(), m_descriptor.Control.Font, m_brush, rect_font, m_sf); + rect_back.Y += m_nItemHeight; + rect_font.Y += m_nItemHeight; + } + g.DrawRectangle(m_pen, 0, 0, this.Width - 1, this.Height - 1); + } + + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + int nIndex = e.Y / m_nItemHeight; + if (nIndex < 0 || nIndex >= m_lst_item.Count) return; + var item = m_lst_item[e.Y / m_nItemHeight]; + if (m_item_hover == item) return; + m_item_hover = item; + this.Invalidate(); + } + + protected override void OnMouseClick(MouseEventArgs e) { + base.OnMouseClick(e); + this.Close(); + int nIndex = e.Y / m_nItemHeight; + if (nIndex < 0) return; + if (nIndex > m_lst_item.Count) return; + try { + m_descriptor.SetValue(m_lst_item[nIndex], null); + } catch (Exception ex) { + m_descriptor.OnSetValueError(ex); + } + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNode.cs b/CodeWalker.WinForms/STNodeEditor/STNode.cs new file mode 100644 index 0000000..c66870b --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNode.cs @@ -0,0 +1,1117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Windows.Forms; +using System.Collections; +/* +MIT License + +Copyright (c) 2021 DebugST@crystal_lz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/* + * create: 2021-12-08 + * modify: 2021-03-02 + * Author: Crystal_lz + * blog: http://st233.com + * Gitee: https://gitee.com/DebugST + * Github: https://github.com/DebugST + */ +namespace ST.Library.UI.NodeEditor +{ + public abstract class STNode + { + private STNodeEditor _Owner; + /// + /// Get the current Node owner + /// + public STNodeEditor Owner { + get { return _Owner; } + internal set { + if (value == _Owner) return; + if (_Owner != null) { + foreach (STNodeOption op in this._InputOptions.ToArray()) op.DisConnectionAll(); + foreach (STNodeOption op in this._OutputOptions.ToArray()) op.DisConnectionAll(); + } + _Owner = value; + if (!this._AutoSize) this.SetOptionsLocation(); + this.BuildSize(true, true, false); + this.OnOwnerChanged(); + } + } + + private bool _IsSelected; + /// + /// Gets or sets whether Node is selected + /// + public bool IsSelected { + get { return _IsSelected; } + set { + if (value == _IsSelected) return; + _IsSelected = value; + this.Invalidate(); + this.OnSelectedChanged(); + if (this._Owner != null) this._Owner.OnSelectedChanged(EventArgs.Empty); + } + } + + private bool _IsActive; + /// + /// Gets if Node is active + /// + public bool IsActive { + get { return _IsActive; } + internal set { + if (value == _IsActive) return; + _IsActive = value; + this.OnActiveChanged(); + } + } + + private Color _TitleColor; + /// + /// Gets or sets the title background color + /// + public Color TitleColor { + get { return _TitleColor; } + protected set { + _TitleColor = value; + this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); + } + } + + private Color _MarkColor; + /// + /// Gets or sets the background color of the marker information + /// + public Color MarkColor { + get { return _MarkColor; } + protected set { + _MarkColor = value; + this.Invalidate(this._MarkRectangle); + } + } + + private Color _ForeColor = Color.White; + /// + /// Get or set the current Node foreground color + /// + public Color ForeColor { + get { return _ForeColor; } + protected set { + _ForeColor = value; + this.Invalidate(); + } + } + + private Color _BackColor; + /// + /// Gets or sets the current Node background color + /// + public Color BackColor { + get { return _BackColor; } + protected set { + _BackColor = value; + this.Invalidate(); + } + } + + private string _Title; + /// + /// Gets or sets the Node title + /// + public string Title { + get { return _Title; } + protected set { + _Title = value; + if (this._AutoSize) this.BuildSize(true, true, true); + //this.Invalidate(this.TitleRectangle); + } + } + + private string _Mark; + /// + /// Get or set Node tag information + /// + public string Mark { + get { return _Mark; } + set { + _Mark = value; + if (value == null) + _MarkLines = null; + else + _MarkLines = (from s in value.Split('\n') select s.Trim()).ToArray(); + this.Invalidate(new Rectangle(-5, -5, this._MarkRectangle.Width + 10, this._MarkRectangle.Height + 10)); + } + } + + private string[] _MarkLines;//Store line data separately without splitting it every time you draw + /// + /// Get Node tag information row data + /// + public string[] MarkLines { + get { return _MarkLines; } + } + + private int _Left; + /// + /// Get or set the left coordinate of Node + /// + public int Left { + get { return _Left; } + set { + if (this._LockLocation || value == _Left) return; + _Left = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnMove(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + } + } + + private int _Top; + /// + /// Gets or sets the coordinates on the top of the Node + /// + public int Top { + get { return _Top; } + set { + if (this._LockLocation || value == _Top) return; + _Top = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnMove(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + } + } + + private int _Width = 100; + /// + /// Gets or sets the Node width. This value cannot be set when AutoSize is set + /// + public int Width { + get { return _Width; } + protected set { + if (value < 50) return; + if (this._AutoSize || value == _Width) return; + _Width = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnResize(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + this.Invalidate(); + } + } + + private int _Height = 40; + /// + /// Gets or sets the height of Node. This value cannot be set when AutoSize is set + /// + public int Height { + get { return _Height; } + protected set { + if (value < 40) return; + if (this._AutoSize || value == _Height) return; + _Height = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnResize(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + this.Invalidate(); + } + } + + private int _ItemHeight = 20; + /// + /// Gets or sets the height of each option of Node + /// + public int ItemHeight { + get { return _ItemHeight; } + protected set { + if (value < 16) value = 16; + if (value > 200) value = 200; + if (value == _ItemHeight) return; + _ItemHeight = value; + if (this._AutoSize) { + this.BuildSize(true, false, true); + } else { + this.SetOptionsLocation(); + if (this._Owner != null) this._Owner.Invalidate(); + } + } + } + + private bool _AutoSize = true; + /// + /// Gets or sets whether Node automatically calculates width and height + /// + public bool AutoSize { + get { return _AutoSize; } + protected set { _AutoSize = value; } + } + /// + /// Get the coordinates of the right edge of Node + /// + public int Right { + get { return _Left + _Width; } + } + /// + /// Get the coordinates below the Node + /// + public int Bottom { + get { return _Top + _Height; } + } + /// + /// Get the Node rectangle area + /// + public Rectangle Rectangle { + get { + return new Rectangle(this._Left, this._Top, this._Width, this._Height); + } + } + /// + /// Get the Node title rectangle area + /// + public Rectangle TitleRectangle { + get { + return new Rectangle(this._Left, this._Top, this._Width, this._TitleHeight); + } + } + + private Rectangle _MarkRectangle; + /// + /// Get the Node marked rectangular area + /// + public Rectangle MarkRectangle { + get { return _MarkRectangle; } + } + + private int _TitleHeight = 20; + /// + /// Gets or sets the height of the Node title + /// + public int TitleHeight { + get { return _TitleHeight; } + protected set { _TitleHeight = value; } + } + + private STNodeOptionCollection _InputOptions; + /// + /// Get the set of input options + /// + protected internal STNodeOptionCollection InputOptions { + get { return _InputOptions; } + } + /// + /// Get the number of input options set + /// + public int InputOptionsCount { get { return _InputOptions.Count; } } + + private STNodeOptionCollection _OutputOptions; + /// + /// Get output options + /// + protected internal STNodeOptionCollection OutputOptions { + get { return _OutputOptions; } + } + /// + /// Get the number of output options + /// + public int OutputOptionsCount { get { return _OutputOptions.Count; } } + + private STNodeControlCollection _Controls; + /// + /// Get the set of controls contained in Node + /// + protected STNodeControlCollection Controls { + get { return _Controls; } + } + /// + /// Get the number of control sets contained in Node + /// + public int ControlsCount { get { return _Controls.Count; } } + /// + /// Get the Node coordinate position + /// + public Point Location { + get { return new Point(this._Left, this._Top); } + set { + this.Left = value.X; + this.Top = value.Y; + } + } + /// + /// Get the Node size + /// + public Size Size { + get { return new Size(this._Width, this._Height); } + set { + this.Width = value.Width; + this.Height = value.Height; + } + } + + private Font _Font; + /// + /// Get or set the Node font + /// + protected Font Font { + get { return _Font; } + set { + if (value == _Font) return; + this._Font.Dispose(); + _Font = value; + } + } + + private bool _LockOption; + /// + /// Get or set whether to lock the Option option and not accept connections after locking + /// + public bool LockOption { + get { return _LockOption; } + set { + _LockOption = value; + this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); + } + } + + private bool _LockLocation; + /// + /// Gets or sets whether to lock the Node position and cannot move after it is locked + /// + public bool LockLocation { + get { return _LockLocation; } + set { + _LockLocation = value; + this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); + } + } + + private ContextMenuStrip _ContextMenuStrip; + /// + /// Gets or sets the current Node context menu + /// + public ContextMenuStrip ContextMenuStrip { + get { return _ContextMenuStrip; } + set { _ContextMenuStrip = value; } + } + + private object _Tag; + /// + /// Get or set user-defined saved data + /// + public object Tag { + get { return _Tag; } + set { _Tag = value; } + } + + private Guid _Guid; + /// + /// Get the global unique ID + /// + public Guid Guid { + get { return _Guid; } + } + + private bool _LetGetOptions = false; + /// + /// Gets or sets whether to allow external access to STNodeOption + /// + public bool LetGetOptions { + get { return _LetGetOptions; } + protected set { _LetGetOptions = value; } + } + + private static Point m_static_pt_init = new Point(10, 10); + + public STNode() { + this._Title = "Untitled"; + this._MarkRectangle.Height = this._Height; + this._Left = this._MarkRectangle.X = m_static_pt_init.X; + this._Top = m_static_pt_init.Y; + this._MarkRectangle.Y = this._Top - 30; + this._InputOptions = new STNodeOptionCollection(this, true); + this._OutputOptions = new STNodeOptionCollection(this, false); + this._Controls = new STNodeControlCollection(this); + this._BackColor = Color.FromArgb(200, 64, 64, 64); + this._TitleColor = Color.FromArgb(200, Color.DodgerBlue); + this._MarkColor = Color.FromArgb(200, Color.Brown); + this._Font = new Font("courier new", 8.25f); + + m_sf = new StringFormat(); + m_sf.Alignment = StringAlignment.Near; + m_sf.LineAlignment = StringAlignment.Center; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + m_sf.SetTabStops(0, new float[] { 40 }); + m_static_pt_init.X += 10; + m_static_pt_init.Y += 10; + this._Guid = Guid.NewGuid(); + this.OnCreate(); + } + + //private int m_nItemHeight = 30; + protected StringFormat m_sf; + /// + /// Active controls in the current Node + /// + protected STNodeControl m_ctrl_active; + /// + /// The hover control in the current Node + /// + protected STNodeControl m_ctrl_hover; + /// + /// The control under the mouse click in the current Node + /// + protected STNodeControl m_ctrl_down; + + protected internal void BuildSize(bool bBuildNode, bool bBuildMark, bool bRedraw) { + if (this._Owner == null) return; + using (Graphics g = this._Owner.CreateGraphics()) { + if (this._AutoSize && bBuildNode) { + Size sz = this.GetDefaultNodeSize(g); + if (this._Width != sz.Width || this._Height != sz.Height) { + this._Width = sz.Width; + this._Height = sz.Height; + this.SetOptionsLocation(); + this.OnResize(EventArgs.Empty); + } + } + if (bBuildMark && !string.IsNullOrEmpty(this._Mark)) { + this._MarkRectangle = this.OnBuildMarkRectangle(g); + } + } + if (bRedraw) this._Owner.Invalidate(); + } + + internal Dictionary OnSaveNode() { + Dictionary dic = new Dictionary(); + dic.Add("Guid", this._Guid.ToByteArray()); + dic.Add("Left", BitConverter.GetBytes(this._Left)); + dic.Add("Top", BitConverter.GetBytes(this._Top)); + dic.Add("Width", BitConverter.GetBytes(this._Width)); + dic.Add("Height", BitConverter.GetBytes(this._Height)); + dic.Add("AutoSize", new byte[] { (byte)(this._AutoSize ? 1 : 0) }); + if (this._Mark != null) dic.Add("Mark", Encoding.UTF8.GetBytes(this._Mark)); + dic.Add("LockOption", new byte[] { (byte)(this._LockLocation ? 1 : 0) }); + dic.Add("LockLocation", new byte[] { (byte)(this._LockLocation ? 1 : 0) }); + Type t = this.GetType(); + foreach (var p in t.GetProperties()) { + var attrs = p.GetCustomAttributes(true); + foreach (var a in attrs) { + if (!(a is STNodePropertyAttribute)) continue; + var attr = a as STNodePropertyAttribute; + object obj = Activator.CreateInstance(attr.DescriptorType); + if (!(obj is STNodePropertyDescriptor)) + throw new InvalidOperationException("[STNodePropertyAttribute.Type] parameter value must be the type of [STNodePropertyDescriptor] or its subclass"); + var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType); + desc.Node = this; + desc.PropertyInfo = p; + byte[] byData = desc.GetBytesFromValue(); + if (byData == null) continue; + dic.Add(p.Name, byData); + } + } + this.OnSaveNode(dic); + return dic; + } + + internal byte[] GetSaveData() { + List lst = new List(); + Type t = this.GetType(); + byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name + "|" + t.FullName); + lst.Add((byte)byData.Length); + lst.AddRange(byData); + byData = Encoding.UTF8.GetBytes(t.GUID.ToString()); + lst.Add((byte)byData.Length); + lst.AddRange(byData); + + var dic = this.OnSaveNode(); + if (dic != null) { + foreach (var v in dic) { + byData = Encoding.UTF8.GetBytes(v.Key); + lst.AddRange(BitConverter.GetBytes(byData.Length)); + lst.AddRange(byData); + lst.AddRange(BitConverter.GetBytes(v.Value.Length)); + lst.AddRange(v.Value); + } + } + return lst.ToArray(); + } + + #region protected + /// + /// Occurs when Node is constructed + /// + protected virtual void OnCreate() { } + /// + /// Draw the entire Node + /// + /// Drawing tool + protected internal virtual void OnDrawNode(DrawingTools dt) { + dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + //Fill background + if (this._BackColor.A != 0) { + dt.SolidBrush.Color = this._BackColor; + dt.Graphics.FillRectangle(dt.SolidBrush, this._Left, this._Top + this._TitleHeight, this._Width, this.Height - this._TitleHeight); + } + this.OnDrawTitle(dt); + this.OnDrawBody(dt); + } + /// + /// Draw the Node header section + /// + /// Drawing tool + protected virtual void OnDrawTitle(DrawingTools dt) { + m_sf.Alignment = StringAlignment.Center; + m_sf.LineAlignment = StringAlignment.Center; + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + if (this._TitleColor.A != 0) { + brush.Color = this._TitleColor; + g.FillRectangle(brush, this.TitleRectangle); + } + if (this._LockOption) { + //dt.Pen.Color = this.ForeColor; + brush.Color = this._ForeColor; + int n = this._Top + this._TitleHeight / 2 - 5; + g.FillRectangle(dt.SolidBrush, this._Left + 4, n + 0, 2, 4); + g.FillRectangle(dt.SolidBrush, this._Left + 6, n + 0, 2, 2); + g.FillRectangle(dt.SolidBrush, this._Left + 8, n + 0, 2, 4); + g.FillRectangle(dt.SolidBrush, this._Left + 3, n + 4, 8, 6); + //g.DrawLine(dt.Pen, this._Left + 6, n + 5, this._Left + 6, n + 7); + //g.DrawRectangle(dt.Pen, this._Left + 3, n + 0, 6, 3); + //g.DrawRectangle(dt.Pen, this._Left + 2, n + 3, 8, 6); + //g.DrawLine(dt.Pen, this._Left + 6, n + 5, this._Left + 6, n + 7); + + } + if (this._LockLocation) { + //dt.Pen.Color = this.ForeColor; + brush.Color = this._ForeColor; + int n = this._Top + this._TitleHeight / 2 - 5; + g.FillRectangle(brush, this.Right - 9, n, 4, 4); + g.FillRectangle(brush, this.Right - 11, n + 4, 8, 2); + g.FillRectangle(brush, this.Right - 8, n + 6, 2, 4); + //g.DrawLine(dt.Pen, this.Right - 10, n + 6, this.Right - 4, n + 6); + //g.DrawLine(dt.Pen, this.Right - 10, n, this.Right - 4, n); + //g.DrawLine(dt.Pen, this.Right - 11, n + 6, this.Right - 3, n + 6); + //g.DrawLine(dt.Pen, this.Right - 7, n + 7, this.Right - 7, n + 9); + } + if (!string.IsNullOrEmpty(this._Title) && this._ForeColor.A != 0) { + brush.Color = this._ForeColor; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.DrawString(this._Title, this._Font, brush, this.TitleRectangle, m_sf); + } + } + /// + /// Draw the Node body part to remove the title part + /// + /// Drawing tool + protected virtual void OnDrawBody(DrawingTools dt) { + SolidBrush brush = dt.SolidBrush; + foreach (STNodeOption op in this._InputOptions) { + if (op == STNodeOption.Empty) continue; + this.OnDrawOptionDot(dt, op); + this.OnDrawOptionText(dt, op); + } + foreach (STNodeOption op in this._OutputOptions) { + if (op == STNodeOption.Empty) continue; + this.OnDrawOptionDot(dt, op); + this.OnDrawOptionText(dt, op); + } + if (this._Controls.Count != 0) { //Draw child controls + // Align the origin of the coordinates with the node + //dt.Graphics.ResetTransform(); + dt.Graphics.TranslateTransform(this._Left, this._Top + this._TitleHeight); + Point pt = Point.Empty; //The current amount of offset needed + Point pt_last = Point.Empty; //The coordinates of the last control relative to the node + foreach (STNodeControl v in this._Controls) { + if (!v.Visable) continue; + pt.X = v.Left - pt_last.X; + pt.Y = v.Top - pt_last.Y; + pt_last = v.Location; + dt.Graphics.TranslateTransform(pt.X, pt.Y); //Move the origin coordinates to the control position + dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + v.OnPaint(dt); + } + //dt.Graphics.TranslateTransform(-pt_last.X, -pt_last.Y); restore coordinates + dt.Graphics.TranslateTransform(-this._Left - pt_last.X, -this._Top - this._TitleHeight - pt_last.Y); + //dt.Graphics. + } + } + /// + /// Draw marker information + /// + /// Drawing tool + protected internal virtual void OnDrawMark(DrawingTools dt) { + if (string.IsNullOrEmpty(this._Mark)) return; + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + m_sf.LineAlignment = StringAlignment.Center; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + brush.Color = this._MarkColor; + g.FillRectangle(brush, this._MarkRectangle); //fill background color + + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //Determine the size required for text drawing + var sz = g.MeasureString(this.Mark, this.Font, this._MarkRectangle.Width); + brush.Color = this._ForeColor; + if (sz.Height > this._ItemHeight || sz.Width > this._MarkRectangle.Width) { //If it exceeds the drawing area, draw the part + Rectangle rect = new Rectangle(this._MarkRectangle.Left + 2, this._MarkRectangle.Top + 2, this._MarkRectangle.Width - 20, 16); + m_sf.Alignment = StringAlignment.Near; + g.DrawString(this._MarkLines[0], this._Font, brush, rect, m_sf); + m_sf.Alignment = StringAlignment.Far; + rect.Width = this._MarkRectangle.Width - 5; + g.DrawString("+", this._Font, brush, rect, m_sf); // + means beyond the drawing area + } else { + m_sf.Alignment = StringAlignment.Near; + g.DrawString(this._MarkLines[0].Trim(), this._Font, brush, this._MarkRectangle, m_sf); + } + } + /// + /// The point where the option line is drawn + /// + /// Drawing tool + /// Specified options + protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op) { + Graphics g = dt.Graphics; + Pen pen = dt.Pen; + SolidBrush brush = dt.SolidBrush; + var t = typeof(object); + if (op.DotColor != Color.Transparent) //Set the color + brush.Color = op.DotColor; + else { + if (op.DataType == t) + pen.Color = this.Owner.UnknownTypeColor; + else + brush.Color = this.Owner.TypeColor.ContainsKey(op.DataType) ? this.Owner.TypeColor[op.DataType] : this.Owner.UnknownTypeColor; + } + if (op.IsSingle) { //Single connection circle + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + if (op.DataType == t) { //unknown type to draw otherwise fill + g.DrawEllipse(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); + } else + g.FillEllipse(brush, op.DotRectangle); + } else { //Multiple connection rectangles + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + if (op.DataType == t) { + g.DrawRectangle(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); + } else + g.FillRectangle(brush, op.DotRectangle); + } + } + /// + /// Text for drawing options + /// + /// Drawing tool + /// Specified options + protected virtual void OnDrawOptionText(DrawingTools dt, STNodeOption op) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + if (op.IsInput) { + m_sf.Alignment = StringAlignment.Near; + } else { + m_sf.Alignment = StringAlignment.Far; + } + brush.Color = op.TextColor; + g.DrawString(op.Text, this.Font, brush, op.TextRectangle, m_sf); + } + /// + /// Occurs when calculating the position of the Option connection point + /// + /// Option to be calculated + /// Automatically calculated position + /// Index of the current Option + /// New location + protected virtual Point OnSetOptionDotLocation(STNodeOption op, Point pt, int nIndex) { + return pt; + } + /// + /// Occurs when evaluating the Option text area + /// + /// Option to be calculated + /// Automatically calculated area + /// Index of the current Option + /// New area + protected virtual Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect, int nIndex) { + return rect; + } + /// + /// Get the default size required by the current STNode + /// The returned size does not limit the drawing area and can still be drawn outside of this area + /// But it will not be accepted by STNodeEditor and trigger the corresponding event + /// + /// Drawing panel + /// Calculated size + protected virtual Size GetDefaultNodeSize(Graphics g) { + int nInputHeight = 0, nOutputHeight = 0; + foreach (STNodeOption op in this._InputOptions) nInputHeight += this._ItemHeight; + foreach (STNodeOption op in this._OutputOptions) nOutputHeight += this._ItemHeight; + int nHeight = this._TitleHeight + (nInputHeight > nOutputHeight ? nInputHeight : nOutputHeight); + + SizeF szf_input = SizeF.Empty, szf_output = SizeF.Empty; + foreach (STNodeOption v in this._InputOptions) { + if (string.IsNullOrEmpty(v.Text)) continue; + SizeF szf = g.MeasureString(v.Text, this._Font); + if (szf.Width > szf_input.Width) szf_input = szf; + } + foreach (STNodeOption v in this._OutputOptions) { + if (string.IsNullOrEmpty(v.Text)) continue; + SizeF szf = g.MeasureString(v.Text, this._Font); + if (szf.Width > szf_output.Width) szf_output = szf; + } + int nWidth = (int)(szf_input.Width + szf_output.Width + 25); + if (!string.IsNullOrEmpty(this.Title)) szf_input = g.MeasureString(this.Title, this.Font); + if (szf_input.Width + 30 > nWidth) nWidth = (int)szf_input.Width + 30; + return new Size(nWidth, nHeight); + } + /// + /// Calculate the rectangular area required by the current Mark + /// The returned size does not limit the drawing area and can still be drawn outside of this area + /// But it will not be accepted by STNodeEditor and trigger the corresponding event + /// + /// Drawing panel + /// Calculated area + protected virtual Rectangle OnBuildMarkRectangle(Graphics g) { + //if (string.IsNullOrEmpty(this._Mark)) return Rectangle.Empty; + return new Rectangle(this._Left, this._Top - 30, this._Width, 20); + } + /// + /// What data does this Node need to save additionally when it needs to be saved? + /// Note: When saving, it will not be serialized and restored. When only re-creating this Node through the empty parameter constructor + /// Then call OnLoadNode() to restore the saved data + /// + /// Data to be saved + protected virtual void OnSaveNode(Dictionary dic) { } + /// + /// When restoring the node, the data returned by OnSaveNode() will be re-introduced to this function + /// + /// Save data + protected internal virtual void OnLoadNode(Dictionary dic) { + if (dic.ContainsKey("AutoSize")) this._AutoSize = dic["AutoSize"][0] == 1; + if (dic.ContainsKey("LockOption")) this._LockOption = dic["LockOption"][0] == 1; + if (dic.ContainsKey("LockLocation")) this._LockLocation = dic["LockLocation"][0] == 1; + if (dic.ContainsKey("Guid")) this._Guid = new Guid(dic["Guid"]); + if (dic.ContainsKey("Left")) this._Left = BitConverter.ToInt32(dic["Left"], 0); + if (dic.ContainsKey("Top")) this._Top = BitConverter.ToInt32(dic["Top"], 0); + if (dic.ContainsKey("Width") && !this._AutoSize) this._Width = BitConverter.ToInt32(dic["Width"], 0); + if (dic.ContainsKey("Height") && !this._AutoSize) this._Height = BitConverter.ToInt32(dic["Height"], 0); + if (dic.ContainsKey("Mark")) this.Mark = Encoding.UTF8.GetString(dic["Mark"]); + Type t = this.GetType(); + foreach (var p in t.GetProperties()) { + var attrs = p.GetCustomAttributes(true); + foreach (var a in attrs) { + if (!(a is STNodePropertyAttribute)) continue; + var attr = a as STNodePropertyAttribute; + object obj = Activator.CreateInstance(attr.DescriptorType); + if (!(obj is STNodePropertyDescriptor)) + throw new InvalidOperationException("[STNodePropertyAttribute.Type] parameter value must be the type of [STNodePropertyDescriptor] or its subclass"); + var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType); + desc.Node = this; + desc.PropertyInfo = p; + try { + if (dic.ContainsKey(p.Name)) desc.SetValue(dic[p.Name]); + } catch (Exception ex) { + string strErr = "The value of attribute [" + this.Title + "." + p.Name + "] cannot be restored by overriding [STNodePropertyAttribute.GetBytesFromValue(), STNodePropertyAttribute.GetValueFromBytes(byte[])] to ensure preservation and The binary data is correct when loading"; + Exception e = ex; + while (e != null) { + strErr += "\r\n----\r\n[" + e.GetType().Name + "] -> " + e.Message; + e = e.InnerException; + } + throw new InvalidOperationException(strErr, ex); + } + } + } + } + /// + /// Occurs when the editor has loaded all nodes + /// + protected internal virtual void OnEditorLoadCompleted() { } + /// + /// Set the text information of Option + /// + /// Target Option + /// text + /// Successful + protected bool SetOptionText(STNodeOption op, string strText) { + if (op.Owner != this) return false; + op.Text = strText; + return true; + } + /// + /// Set the color of the Option text information + /// + /// Target Option + /// color + /// Successful + protected bool SetOptionTextColor(STNodeOption op, Color clr) { + if (op.Owner != this) return false; + op.TextColor = clr; + return true; + } + /// + /// Set the color of the Option connection point + /// + /// Target Option + /// color + /// Successful + protected bool SetOptionDotColor(STNodeOption op, Color clr) { + if (op.Owner != this) return false; + op.DotColor = clr; + return false; + } + + //[event]===========================[event]==============================[event]============================[event] + + protected internal virtual void OnGotFocus(EventArgs e) { } + + protected internal virtual void OnLostFocus(EventArgs e) { } + + protected internal virtual void OnMouseEnter(EventArgs e) { } + + protected internal virtual void OnMouseDown(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + for (int i = this._Controls.Count - 1; i >= 0; i--) { + var c = this._Controls[i]; + if (c.DisplayRectangle.Contains(pt)) { + if (!c.Enabled) return; + if (!c.Visable) continue; + c.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); + m_ctrl_down = c; + if (m_ctrl_active != c) { + c.OnGotFocus(EventArgs.Empty); + if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(EventArgs.Empty); + m_ctrl_active = c; + } + return; + } + } + if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(EventArgs.Empty); + m_ctrl_active = null; + } + + protected internal virtual void OnMouseMove(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_down != null) { + if (m_ctrl_down.Enabled && m_ctrl_down.Visable) + m_ctrl_down.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_down.Left, pt.Y - m_ctrl_down.Top, e.Delta)); + return; + } + for (int i = this._Controls.Count - 1; i >= 0; i--) { + var c = this._Controls[i]; + if (c.DisplayRectangle.Contains(pt)) { + if (m_ctrl_hover != this._Controls[i]) { + c.OnMouseEnter(EventArgs.Empty); + if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(EventArgs.Empty); + m_ctrl_hover = c; + } + m_ctrl_hover.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); + return; + } + } + if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(EventArgs.Empty); + m_ctrl_hover = null; + } + + protected internal virtual void OnMouseUp(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_down != null && m_ctrl_down.Enabled && m_ctrl_down.Visable) { + m_ctrl_down.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_down.Left, pt.Y - m_ctrl_down.Top, e.Delta)); + } + //if (m_ctrl_active != null) { + // m_ctrl_active.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, + // e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta)); + //} + m_ctrl_down = null; + } + + protected internal virtual void OnMouseLeave(EventArgs e) { + if (m_ctrl_hover != null && m_ctrl_hover.Enabled && m_ctrl_hover.Visable) m_ctrl_hover.OnMouseLeave(e); + m_ctrl_hover = null; + } + + protected internal virtual void OnMouseClick(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) + m_ctrl_active.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta)); + } + + protected internal virtual void OnMouseWheel(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_hover != null && m_ctrl_active.Enabled && m_ctrl_hover.Visable) { + m_ctrl_hover.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_hover.Left, pt.Y - m_ctrl_hover.Top, e.Delta)); + return; + } + } + protected internal virtual void OnMouseHWheel(MouseEventArgs e) { + if (m_ctrl_hover != null && m_ctrl_active.Enabled && m_ctrl_hover.Visable) { + m_ctrl_hover.OnMouseHWheel(e); + return; + } + } + + protected internal virtual void OnKeyDown(KeyEventArgs e) { + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) m_ctrl_active.OnKeyDown(e); + } + protected internal virtual void OnKeyUp(KeyEventArgs e) { + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) m_ctrl_active.OnKeyUp(e); + } + protected internal virtual void OnKeyPress(KeyPressEventArgs e) { + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) m_ctrl_active.OnKeyPress(e); + } + + protected virtual void OnMove(EventArgs e) { /*this.SetOptionLocation();*/ } + protected virtual void OnResize(EventArgs e) { /*this.SetOptionLocation();*/ } + + + /// + /// Occurs when the owner changes + /// + protected virtual void OnOwnerChanged() { } + /// + /// Occurs when the selected state changes + /// + protected virtual void OnSelectedChanged() { } + /// + /// Occurs when the activity state changes + /// + protected virtual void OnActiveChanged() { } + + #endregion protected + /// + /// Calculate the position of each Option + /// + protected virtual void SetOptionsLocation() { + int nIndex = 0; + Rectangle rect = new Rectangle(this.Left + 10, this._Top + this._TitleHeight, this._Width - 20, this._ItemHeight); + foreach (STNodeOption op in this._InputOptions) { + if (op != STNodeOption.Empty) { + Point pt = this.OnSetOptionDotLocation(op, new Point(this.Left - op.DotSize / 2, rect.Y + (rect.Height - op.DotSize) / 2), nIndex); + op.TextRectangle = this.OnSetOptionTextRectangle(op, rect, nIndex); + op.DotLeft = pt.X; + op.DotTop = pt.Y; + } + rect.Y += this._ItemHeight; + nIndex++; + } + rect.Y = this._Top + this._TitleHeight; + m_sf.Alignment = StringAlignment.Far; + foreach (STNodeOption op in this._OutputOptions) { + if (op != STNodeOption.Empty) { + Point pt = this.OnSetOptionDotLocation(op, new Point(this._Left + this._Width - op.DotSize / 2, rect.Y + (rect.Height - op.DotSize) / 2), nIndex); + op.TextRectangle = this.OnSetOptionTextRectangle(op, rect, nIndex); + op.DotLeft = pt.X; + op.DotTop = pt.Y; + } + rect.Y += this._ItemHeight; + nIndex++; + } + } + + /// + /// Redraw Node + /// + public void Invalidate() { + if (this._Owner != null) { + this._Owner.Invalidate(this._Owner.CanvasToControl(new Rectangle(this._Left - 5, this._Top - 5, this._Width + 10, this._Height + 10))); + } + } + /// + /// Redraw the specified area of ​​Node + /// + /// Node specified area + public void Invalidate(Rectangle rect) { + rect.X += this._Left; + rect.Y += this._Top; + if (this._Owner != null) { + rect = this._Owner.CanvasToControl(rect); + rect.Width += 1; rect.Height += 1;//Coordinate system conversion may cause progress loss plus one more pixel + this._Owner.Invalidate(rect); + } + } + /// + /// Get the input Option collection contained in this Node + /// + /// Option array + public STNodeOption[] GetInputOptions() { + if (!this._LetGetOptions) return null; + STNodeOption[] ops = new STNodeOption[this._InputOptions.Count]; + for (int i = 0; i < this._InputOptions.Count; i++) ops[i] = this._InputOptions[i]; + return ops; + } + /// + /// Get the output Option set contained in this Node + /// + /// Option array + public STNodeOption[] GetOutputOptions() { + if (!this._LetGetOptions) return null; + STNodeOption[] ops = new STNodeOption[this._OutputOptions.Count]; + for (int i = 0; i < this._OutputOptions.Count; i++) ops[i] = this._OutputOptions[i]; + return ops; + } + /// + /// Set the selected state of Node + /// + /// Selected + /// Whether to redraw + public void SetSelected(bool bSelected, bool bRedraw) { + if (this._IsSelected == bSelected) return; + this._IsSelected = bSelected; + if (this._Owner != null) { + if (bSelected) + this._Owner.AddSelectedNode(this); + else + this._Owner.RemoveSelectedNode(this); + } + if (bRedraw) this.Invalidate(); + this.OnSelectedChanged(); + if (this._Owner != null) this._Owner.OnSelectedChanged(EventArgs.Empty); + } + public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); } + public IAsyncResult BeginInvoke(Delegate method, params object[] args) { + if (this._Owner == null) return null; + return this._Owner.BeginInvoke(method, args); + } + public object Invoke(Delegate method) { return this.Invoke(method, null); } + public object Invoke(Delegate method, params object[] args) { + if (this._Owner == null) return null; + return this._Owner.Invoke(method, args); + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeAttribute.cs b/CodeWalker.WinForms/STNodeEditor/STNodeAttribute.cs new file mode 100644 index 0000000..cc05b89 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeAttribute.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace ST.Library.UI.NodeEditor +{ + /// + /// STNode node characteristics + /// Used to describe STNode developer information and some behaviors + /// + public class STNodeAttribute : Attribute + { + private string _Path; + /// + /// Get the path that the STNode node expects in the tree control + /// + public string Path { + get { return _Path; } + } + + private string _Author; + /// + /// Get the author name of the STNode node + /// + public string Author { + get { return _Author; } + } + + private string _Mail; + /// + /// Get the author mailbox of the STNode node + /// + public string Mail { + get { return _Mail; } + } + + private string _Link; + /// + /// Get the author link of the STNode node + /// + public string Link { + get { return _Link; } + } + + private string _Description; + /// + /// Get the description information of the STNode node + /// + public string Description { + get { return _Description; } + } + + private static char[] m_ch_splitter = new char[] { '/', '\\' }; + private static Regex m_reg = new Regex(@"^https?://", RegexOptions.IgnoreCase); + /// + /// Constructs an STNode property + /// + /// expected path + public STNodeAttribute(string strPath) : this(strPath, null, null, null, null) { } + /// + /// Constructs an STNode property + /// + /// expected path + /// Description + public STNodeAttribute(string strPath, string strDescription) : this(strPath, null, null, null, strDescription) { } + /// + /// Constructs an STNode property + /// + /// expected path + /// STNode author name + /// STNode author mailbox + /// STNode author link + /// STNode node description information + public STNodeAttribute(string strPath, string strAuthor, string strMail, string strLink, string strDescription) { + if (!string.IsNullOrEmpty(strPath)) + strPath = strPath.Trim().Trim(m_ch_splitter).Trim(); + + this._Path = strPath; + + this._Author = strAuthor; + this._Mail = strMail; + this._Description = strDescription; + if (string.IsNullOrEmpty(strLink) || strLink.Trim() == string.Empty) return; + strLink = strLink.Trim(); + if (m_reg.IsMatch(strLink)) + this._Link = strLink; + else + this._Link = "http://" + strLink; + } + + private static Dictionary m_dic = new Dictionary(); + /// + /// Get type helper function + /// + /// Node Type + /// Function information + public static MethodInfo GetHelpMethod(Type stNodeType) { + if (m_dic.ContainsKey(stNodeType)) return m_dic[stNodeType]; + var mi = stNodeType.GetMethod("ShowHelpInfo"); + if (mi == null) return null; + if (!mi.IsStatic) return null; + var ps = mi.GetParameters (); + if (ps.Length != 1) return null; + if (ps[0].ParameterType != typeof(string)) return null; + m_dic.Add(stNodeType, mi); + return mi; + } + /// + /// Execute the helper function for the corresponding node type + /// + /// Node Type + public static void ShowHelp(Type stNodeType) { + var mi = STNodeAttribute.GetHelpMethod (stNodeType); + if (mi == null) return; + mi.Invoke(null, new object[] { stNodeType.Module.FullyQualifiedName }); + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeCollection.cs b/CodeWalker.WinForms/STNodeEditor/STNodeCollection.cs new file mode 100644 index 0000000..fc33c1d --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeCollection.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using System.Drawing; + +namespace ST.Library.UI.NodeEditor +{ + public class STNodeCollection : IList, ICollection, IEnumerable + { + private int _Count; + public int Count { get { return _Count; } } + private STNode[] m_nodes; + private STNodeEditor m_owner; + + internal STNodeCollection(STNodeEditor owner) { + if (owner == null) throw new ArgumentNullException("Owner cannot be null"); + m_owner = owner; + m_nodes = new STNode[4]; + } + + public void MoveToEnd(STNode node) { + if (this._Count < 1) return; + if (m_nodes[this._Count - 1] == node) return; + bool bFound = false; + for (int i = 0; i < _Count - 1; i++) { + if (m_nodes[i] == node) { + bFound = true; + } + if (bFound) m_nodes[i] = m_nodes[i + 1]; + } + m_nodes[this._Count - 1] = node; + } + + public int Add(STNode node) { + if (node == null) throw new ArgumentNullException("Add object cannot be null"); + this.EnsureSpace(1); + int nIndex = this.IndexOf(node); + if (-1 == nIndex) { + nIndex = this._Count; + node.Owner = m_owner; + //node.BuildSize(true, true, false); + m_nodes[this._Count++] = node; + m_owner.BuildBounds(); + m_owner.OnNodeAdded(new STNodeEditorEventArgs(node)); + m_owner.Invalidate(); + //m_owner.Invalidate(m_owner.CanvasToControl(new Rectangle(node.Left - 5, node.Top - 5, node.Width + 10, node.Height + 10))); + //Console.WriteLine(node.Rectangle); + } + return nIndex; + } + + public void AddRange(STNode[] nodes) { + if (nodes == null) throw new ArgumentNullException("Add object cannot be null"); + this.EnsureSpace(nodes.Length); + foreach (var n in nodes) { + if (n == null) throw new ArgumentNullException("Add object cannot be null"); + if (-1 == this.IndexOf(n)) { + n.Owner = m_owner; + m_nodes[this._Count++] = n; + } + m_owner.OnNodeAdded(new STNodeEditorEventArgs(n)); + } + m_owner.Invalidate(); + m_owner.BuildBounds(); + } + + public void Clear() { + for (int i = 0; i < this._Count; i++) { + m_nodes[i].Owner = null; + foreach (STNodeOption op in m_nodes[i].InputOptions) op.DisConnectionAll(); + foreach (STNodeOption op in m_nodes[i].OutputOptions) op.DisConnectionAll(); + m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[i])); + m_owner.InternalRemoveSelectedNode(m_nodes[i]); + } + this._Count = 0; + m_nodes = new STNode[4]; + m_owner.SetActiveNode(null); + m_owner.BuildBounds(); + m_owner.ScaleCanvas(1, 0, 0); //The coordinate system returns when there is no node + m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All); + m_owner.Invalidate(); //If the canvas position and zoom are in the initial state, the above two lines of code will not cause the control to redraw + } + + public bool Contains(STNode node) { + return this.IndexOf(node) != -1; + } + + public int IndexOf(STNode node) { + return Array.IndexOf(m_nodes, node); + } + + public void Insert(int nIndex, STNode node) { + if (nIndex < 0 || nIndex >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + if (node == null) + throw new ArgumentNullException("Insert object cannot be null"); + this.EnsureSpace(1); + for (int i = this._Count; i > nIndex; i--) + m_nodes[i] = m_nodes[i - 1]; + node.Owner = m_owner; + m_nodes[nIndex] = node; + this._Count++; + //node.BuildSize(true, true,false); + m_owner.Invalidate(); + m_owner.BuildBounds(); + } + + public bool IsFixedSize { + get { return false; } + } + + public bool IsReadOnly { + get { return false; } + } + + public void Remove(STNode node) { + int nIndex = this.IndexOf(node); + if (nIndex != -1) this.RemoveAt(nIndex); + } + + public void RemoveAt(int nIndex) { + if (nIndex < 0 || nIndex >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + m_nodes[nIndex].Owner = null; + m_owner.InternalRemoveSelectedNode(m_nodes[nIndex]); + if (m_owner.ActiveNode == m_nodes[nIndex]) m_owner.SetActiveNode(null); + m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[nIndex])); + this._Count--; + for (int i = nIndex, Len = this._Count; i < Len; i++) + m_nodes[i] = m_nodes[i + 1]; + if (this._Count == 0) { //The coordinate system returns when there is no node + m_owner.ScaleCanvas(1, 0, 0); + m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All); + } else { + m_owner.Invalidate(); + m_owner.BuildBounds(); + } + } + + public STNode this[int nIndex] { + get { + if (nIndex < 0 || nIndex >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + return m_nodes[nIndex]; + } + set { throw new InvalidOperationException("No reassignment of elements"); } + } + + public void CopyTo(Array array, int index) { + if (array == null) + throw new ArgumentNullException("Array cannot be empty"); + m_nodes.CopyTo(array, index); + } + + public bool IsSynchronized { + get { return true; } + } + + public object SyncRoot { + get { return this; } + } + + public IEnumerator GetEnumerator() { + for (int i = 0, Len = this._Count; i < Len; i++) + yield return m_nodes[i]; + } + /// + /// Check if there is enough space to expand the capacity + /// + /// Number of elements to be added + private void EnsureSpace(int elements) { + if (elements + this._Count > m_nodes.Length) { + STNode[] arrTemp = new STNode[Math.Max(m_nodes.Length * 2, elements + this._Count)]; + m_nodes.CopyTo(arrTemp, 0); + m_nodes = arrTemp; + } + } + //============================================================================ + int IList.Add(object value) { + return this.Add((STNode)value); + } + + void IList.Clear() { + this.Clear(); + } + + bool IList.Contains(object value) { + return this.Contains((STNode)value); + } + + int IList.IndexOf(object value) { + return this.IndexOf((STNode)value); + } + + void IList.Insert(int index, object value) { + this.Insert(index, (STNode)value); + } + + bool IList.IsFixedSize { + get { return this.IsFixedSize; } + } + + bool IList.IsReadOnly { + get { return this.IsReadOnly; } + } + + void IList.Remove(object value) { + this.Remove((STNode)value); + } + + void IList.RemoveAt(int index) { + this.RemoveAt(index); + } + + object IList.this[int index] { + get { + return this[index]; + } + set { + this[index] = (STNode)value; + } + } + + void ICollection.CopyTo(Array array, int index) { + this.CopyTo(array, index); + } + + int ICollection.Count { + get { return this._Count; } + } + + bool ICollection.IsSynchronized { + get { return this.IsSynchronized; } + } + + object ICollection.SyncRoot { + get { return this.SyncRoot; } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + + public STNode[] ToArray() { + STNode[] nodes = new STNode[this._Count]; + for (int i = 0; i < nodes.Length; i++) + nodes[i] = m_nodes[i]; + return nodes; + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeControl.cs b/CodeWalker.WinForms/STNodeEditor/STNodeControl.cs new file mode 100644 index 0000000..6a6c4b1 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeControl.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.Drawing; +/* +MIT License + +Copyright (c) 2021 DebugST@crystal_lz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/* + * time: 2021-01-06 + * Author: Crystal_lz + * blog: st233.com + * Github: DebugST.github.io + */ +namespace ST.Library.UI.NodeEditor +{ + public class STNodeControl + { + private STNode _Owner; + + public STNode Owner { + get { return _Owner; } + internal set { _Owner = value; } + } + + private int _Left; + + public int Left { + get { return _Left; } + set { + _Left = value; + this.OnMove(EventArgs.Empty); + this.Invalidate(); + } + } + + private int _Top; + + public int Top { + get { return _Top; } + set { + _Top = value; + this.OnMove(EventArgs.Empty); + this.Invalidate(); + } + } + + private int _Width; + + public int Width { + get { return _Width; } + set { + _Width = value; + this.OnResize(EventArgs.Empty); + this.Invalidate(); + } + } + + private int _Height; + + public int Height { + get { return _Height; } + set { + _Height = value; + this.OnResize(EventArgs.Empty); + this.Invalidate(); + } + } + + public int Right { get { return this._Left + this._Width; } } + + public int Bottom { get { return this._Top + this._Height; } } + + public Point Location { + get { return new Point(this._Left, this._Top); } + set { + this.Left = value.X; + this.Top = value.Y; + } + } + public Size Size { + get { return new Size(this._Width, this._Height); } + set { + this.Width = value.Width; + this.Height = value.Height; + } + } + public Rectangle DisplayRectangle { + get { return new Rectangle(this._Left, this._Top, this._Width, this._Height); } + set { + this.Left = value.X; + this.Top = value.Y; + this.Width = value.Width; + this.Height = value.Height; + } + } + public Rectangle ClientRectangle { + get { return new Rectangle(0, 0, this._Width, this._Height); } + } + + private Color _BackColor = Color.FromArgb(127, 0, 0, 0); + + public Color BackColor { + get { return _BackColor; } + set { + _BackColor = value; + this.Invalidate(); + } + } + + private Color _ForeColor = Color.White; + + public Color ForeColor { + get { return _ForeColor; } + set { + _ForeColor = value; + this.Invalidate(); + } + } + + private string _Text = "STNCTRL"; + + public string Text { + get { return _Text; } + set { + _Text = value; + this.Invalidate(); + } + } + + private Font _Font; + + public Font Font { + get { return _Font; } + set { + if (value == _Font) return; + if (value == null) throw new ArgumentNullException("Value cannot be null"); + _Font = value; + this.Invalidate(); + } + } + + private bool _Enabled = true; + + public bool Enabled { + get { return _Enabled; } + set { + if (value == _Enabled) return; + _Enabled = value; + this.Invalidate(); + } + } + + private bool _Visable = true; + + public bool Visable { + get { return _Visable; } + set { + if (value == _Visable) return; + _Visable = value; + this.Invalidate(); + } + } + + protected StringFormat m_sf; + + public STNodeControl() { + m_sf = new StringFormat(); + m_sf.Alignment = StringAlignment.Center; + m_sf.LineAlignment = StringAlignment.Center; + this._Font = new Font("courier new", 8.25f); + this.Width = 75; + this.Height = 23; + } + + protected internal virtual void OnPaint(DrawingTools dt) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + brush.Color = this._BackColor; + g.FillRectangle(brush, 0, 0, this.Width, this.Height); + if (!string.IsNullOrEmpty(this._Text)) { + brush.Color = this._ForeColor; + g.DrawString(this._Text, this._Font, brush, this.ClientRectangle, m_sf); + } + if (this.Paint != null) this.Paint(this, new STNodeControlPaintEventArgs(dt)); + } + + public void Invalidate() { + if (this._Owner == null) return; + this._Owner.Invalidate(new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height)); + } + + public void Invalidate(Rectangle rect) { + if (this._Owner == null) return; + this._Owner.Invalidate(this.RectangleToParent(rect)); + } + + public Rectangle RectangleToParent(Rectangle rect) { + return new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height); + } + + public event EventHandler GotFocus; + public event EventHandler LostFocus; + public event EventHandler MouseEnter; + public event EventHandler MouseLeave; + public event MouseEventHandler MouseDown; + public event MouseEventHandler MouseMove; + public event MouseEventHandler MouseUp; + public event MouseEventHandler MouseClick; + public event MouseEventHandler MouseWheel; + public event EventHandler MouseHWheel; + + public event KeyEventHandler KeyDown; + public event KeyEventHandler KeyUp; + public event KeyPressEventHandler KeyPress; + + public event EventHandler Move; + public event EventHandler Resize; + + public event STNodeControlPaintEventHandler Paint; + + protected internal virtual void OnGotFocus(EventArgs e) { + if (this.GotFocus != null) this.GotFocus(this, e); + } + protected internal virtual void OnLostFocus(EventArgs e) { + if (this.LostFocus != null) this.LostFocus(this, e); + } + protected internal virtual void OnMouseEnter(EventArgs e) { + if (this.MouseEnter != null) this.MouseEnter(this, e); + } + protected internal virtual void OnMouseLeave(EventArgs e) { + if (this.MouseLeave != null) this.MouseLeave(this, e); + } + protected internal virtual void OnMouseDown(MouseEventArgs e) { + if (this.MouseDown != null) this.MouseDown(this, e); + } + protected internal virtual void OnMouseMove(MouseEventArgs e) { + if (this.MouseMove != null) this.MouseMove(this, e); + } + protected internal virtual void OnMouseUp(MouseEventArgs e) { + if (this.MouseUp != null) this.MouseUp(this, e); + } + protected internal virtual void OnMouseClick(MouseEventArgs e) { + if (this.MouseClick != null) this.MouseClick(this, e); + } + protected internal virtual void OnMouseWheel(MouseEventArgs e) { + if (this.MouseWheel != null) this.MouseWheel(this, e); + } + protected internal virtual void OnMouseHWheel(MouseEventArgs e) { + if (this.MouseHWheel != null) this.MouseHWheel(this, e); + } + + protected internal virtual void OnKeyDown(KeyEventArgs e) { + if (this.KeyDown != null) this.KeyDown(this, e); + } + protected internal virtual void OnKeyUp(KeyEventArgs e) { + if (this.KeyUp != null) this.KeyUp(this, e); + } + protected internal virtual void OnKeyPress(KeyPressEventArgs e) { + if (this.KeyPress != null) this.KeyPress(this, e); + } + + protected internal virtual void OnMove(EventArgs e) { + if (this.Move != null) this.Move(this, e); + } + + protected internal virtual void OnResize(EventArgs e) { + if (this.Resize != null) this.Resize(this, e); + } + + public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); } + public IAsyncResult BeginInvoke(Delegate method, params object[] args) { + if (this._Owner == null) return null; + return this._Owner.BeginInvoke(method, args); + } + public object Invoke(Delegate method) { return this.Invoke(method, null); } + public object Invoke(Delegate method, params object[] args) { + if (this._Owner == null) return null; + return this._Owner.Invoke(method, args); + } + } + + public delegate void STNodeControlPaintEventHandler(object sender, STNodeControlPaintEventArgs e); + + public class STNodeControlPaintEventArgs : EventArgs + { + /// + /// Drawing tools + /// + public DrawingTools DrawingTools { get; private set; } + + public STNodeControlPaintEventArgs(DrawingTools dt) { + this.DrawingTools = dt; + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeControlCollection.cs b/CodeWalker.WinForms/STNodeEditor/STNodeControlCollection.cs new file mode 100644 index 0000000..1b0c61f --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeControlCollection.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; + +namespace ST.Library.UI.NodeEditor +{ + public class STNodeControlCollection: IList, ICollection, IEnumerable + { + /* + * To ensure security, only inheritors can access the collection in STNode + */ + private int _Count; + public int Count { get { return _Count; } } + private STNodeControl[] m_controls; + private STNode m_owner; + + internal STNodeControlCollection(STNode owner) { + if (owner == null) throw new ArgumentNullException("Owner cannot be null"); + m_owner = owner; + m_controls = new STNodeControl[4]; + } + + public int Add(STNodeControl control) { + if (control == null) throw new ArgumentNullException("Add object cannot be null"); + this.EnsureSpace(1); + int nIndex = this.IndexOf(control); + if (-1 == nIndex) { + nIndex = this._Count; + control.Owner = m_owner; + m_controls[this._Count++] = control; + this.Redraw(); + } + return nIndex; + } + + public void AddRange(STNodeControl[] controls) { + if (controls == null) throw new ArgumentNullException("Add object cannot be null"); + this.EnsureSpace(controls.Length); + foreach (var op in controls) { + if (op == null) throw new ArgumentNullException("Add object cannot be null"); + if (-1 == this.IndexOf(op)) { + op.Owner = m_owner; + m_controls[this._Count++] = op; + } + } + this.Redraw(); + } + + public void Clear() { + for (int i = 0; i < this._Count; i++) m_controls[i].Owner = null; + this._Count = 0; + m_controls = new STNodeControl[4]; + this.Redraw(); + } + + public bool Contains(STNodeControl option) { + return this.IndexOf(option) != -1; + } + + public int IndexOf(STNodeControl option) { + return Array.IndexOf(m_controls, option); + } + + public void Insert(int index, STNodeControl control) { + if (index < 0 || index >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + if (control == null) + throw new ArgumentNullException("Insert object cannot be null"); + this.EnsureSpace(1); + for (int i = this._Count; i > index; i--) + m_controls[i] = m_controls[i - 1]; + control.Owner = m_owner; + m_controls[index] = control; + this._Count++; + this.Redraw(); + } + + public bool IsFixedSize { + get { return false; } + } + + public bool IsReadOnly { + get { return false; } + } + + public void Remove(STNodeControl control) { + int nIndex = this.IndexOf(control); + if (nIndex != -1) this.RemoveAt(nIndex); + } + + public void RemoveAt(int index) { + if (index < 0 || index >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + this._Count--; + m_controls[index].Owner = null; + for (int i = index, Len = this._Count; i < Len; i++) + m_controls[i] = m_controls[i + 1]; + this.Redraw(); + } + + public STNodeControl this[int index] { + get { + if (index < 0 || index >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + return m_controls[index]; + } + set { throw new InvalidOperationException("No reassignment of elements"); } + } + + public void CopyTo(Array array, int index) { + if (array == null) + throw new ArgumentNullException("Array cannot be empty"); + m_controls.CopyTo(array, index); + } + + public bool IsSynchronized { + get { return true; } + } + + public object SyncRoot { + get { return this; } + } + + public IEnumerator GetEnumerator() { + for (int i = 0, Len = this._Count; i < Len; i++) + yield return m_controls[i]; + } + /// + /// Check if there is enough space to expand the capacity + /// + /// Number of elements to be added + private void EnsureSpace(int elements) { + if (elements + this._Count > m_controls.Length) { + STNodeControl[] arrTemp = new STNodeControl[Math.Max(m_controls.Length * 2, elements + this._Count)]; + m_controls.CopyTo(arrTemp, 0); + m_controls = arrTemp; + } + } + + protected void Redraw() { + if (m_owner != null && m_owner.Owner != null) { + //m_owner.BuildSize(); + m_owner.Owner.Invalidate(m_owner.Owner.CanvasToControl(m_owner.Rectangle)); + } + } + //=================================================================================== + int IList.Add(object value) { + return this.Add((STNodeControl)value); + } + + void IList.Clear() { + this.Clear(); + } + + bool IList.Contains(object value) { + return this.Contains((STNodeControl)value); + } + + int IList.IndexOf(object value) { + return this.IndexOf((STNodeControl)value); + } + + void IList.Insert(int index, object value) { + this.Insert(index, (STNodeControl)value); + } + + bool IList.IsFixedSize { + get { return this.IsFixedSize; } + } + + bool IList.IsReadOnly { + get { return this.IsReadOnly; } + } + + void IList.Remove(object value) { + this.Remove((STNodeControl)value); + } + + void IList.RemoveAt(int index) { + this.RemoveAt(index); + } + + object IList.this[int index] { + get { + return this[index]; + } + set { + this[index] = (STNodeControl)value; + } + } + + void ICollection.CopyTo(Array array, int index) { + this.CopyTo(array, index); + } + + int ICollection.Count { + get { return this._Count; } + } + + bool ICollection.IsSynchronized { + get { return this.IsSynchronized; } + } + + object ICollection.SyncRoot { + get { return this.SyncRoot; } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeEditor.cs b/CodeWalker.WinForms/STNodeEditor/STNodeEditor.cs new file mode 100644 index 0000000..e00ba7e --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeEditor.cs @@ -0,0 +1,2109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.IO; +using System.Windows.Forms; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Threading; +using System.ComponentModel; +using System.Reflection; +using System.IO.Compression; +/* +MIT License + +Copyright (c) 2021 DebugST@crystal_lz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/* + * create: 2020-12-08 + * modify: 2021-04-12 + * Author: Crystal_lz + * blog: http://st233.com + * Gitee: https://gitee.com/DebugST + * Github: https://github.com/DebugST + */ +namespace ST.Library.UI.NodeEditor +{ + public class STNodeEditor : Control + { + private const UInt32 WM_MOUSEHWHEEL = 0x020E; + protected static readonly Type m_type_node = typeof(STNode); + + #region protected enum,struct -------------------------------------------------------------------------------------- + + protected enum CanvasAction //Which of the following actions is performed by the current mouse movement operation + { + None, //None + MoveNode, // is moving Node + ConnectOption, //Connecting Option + SelectRectangle, // is selecting a rectangular area + DrawMarkDetails //Drawing mark information details + } + + protected struct MagnetInfo + { + public bool XMatched; //Whether there is a magnet matching on the X axis + public bool YMatched; + public int X; //match the number on the X axis + public int Y; + public int OffsetX; //The relative offset between the current node X position and the matching X + public int OffsetY; + } + + #endregion + + #region Properties ------------------------------------------------------------------------------------------------------ + + private float _CanvasOffsetX; + /// + /// Get the offset position of the canvas origin relative to the X direction of the control + /// + [Browsable(false)] + public float CanvasOffsetX { + get { return _CanvasOffsetX; } + } + + private float _CanvasOffsetY; + /// + /// Get the offset position of the canvas origin relative to the Y direction of the control + /// + [Browsable(false)] + public float CanvasOffsetY { + get { return _CanvasOffsetY; } + } + + private PointF _CanvasOffset; + /// + /// Get the offset position of the canvas origin relative to the control + /// + [Browsable(false)] + public PointF CanvasOffset { + get { + _CanvasOffset.X = _CanvasOffsetX; + _CanvasOffset.Y = _CanvasOffsetY; + return _CanvasOffset; + } + } + + private Rectangle _CanvasValidBounds; + /// + /// Get the valid area in the canvas that is used + /// + [Browsable(false)] + public Rectangle CanvasValidBounds { + get { return _CanvasValidBounds; } + } + + private float _CanvasScale = 1; + /// + /// Get the zoom ratio of the canvas + /// + [Browsable(false)] + public float CanvasScale { + get { return _CanvasScale; } + } + + private float _Curvature = 0.3F; + /// + /// Gets or sets the curvature of the connection between Option + /// + [Browsable(false)] + public float Curvature { + get { return _Curvature; } + set { + if (value < 0) value = 0; + if (value > 1) value = 1; + _Curvature = value; + if (m_dic_gp_info.Count != 0) this.BuildLinePath(); + } + } + + private bool _ShowMagnet = true; + /// + /// Gets or sets whether to enable the magnet effect when moving the Node in the canvas + /// + [Description("Get or set whether to enable the magnet effect when moving Node in the canvas"), DefaultValue(true)] + public bool ShowMagnet { + get { return _ShowMagnet; } + set { _ShowMagnet = value; } + } + + private bool _ShowBorder = true; + /// + /// Gets or sets whether to display the Node border in the mobile canvas + /// + [Description("Get or set whether to display the Node border in the mobile canvas"), DefaultValue(true)] + public bool ShowBorder { + get { return _ShowBorder; } + set { + _ShowBorder = value; + this.Invalidate(); + } + } + + private bool _ShowGrid = true; + /// + /// Gets or sets whether to draw background grid lines in the canvas + /// + [Description("Get or set whether to draw background grid lines in the canvas"), DefaultValue(true)] + public bool ShowGrid { + get { return _ShowGrid; } + set { + _ShowGrid = value; + this.Invalidate(); + } + } + + private bool _ShowLocation = true; + /// + /// Gets or sets whether to display Node position information beyond the viewing angle at the edge of the canvas + /// + [Description("Get or set whether to display Node position information beyond the viewing angle at the edge of the canvas"), DefaultValue(true)] + public bool ShowLocation { + get { return _ShowLocation; } + set { + _ShowLocation = value; + this.Invalidate(); + } + } + + private STNodeCollection _Nodes; + /// + /// Get the Node collection in the canvas + /// + [Browsable(false)] + public STNodeCollection Nodes { + get { + return _Nodes; + } + } + + private STNode _ActiveNode; + /// + /// Get the active Node selected in the current canvas + /// + [Browsable(false)] + public STNode ActiveNode { + get { return _ActiveNode; } + //set { + // if (value == _ActiveSelectedNode) return; + // if (_ActiveSelectedNode != null) _ActiveSelectedNode.OnLostFocus(EventArgs.Empty); + // _ActiveSelectedNode = value; + // _ActiveSelectedNode.IsActive = true; + // this.Invalidate(); + // this.OnSelectedChanged(EventArgs.Empty); + //} + } + + private STNode _HoverNode; + /// + /// Get the Node that the mouse hovers over in the current canvas + /// + [Browsable(false)] + public STNode HoverNode { + get { return _HoverNode; } + } + //========================================color================================ + private Color _GridColor = Color.Black; + /// + /// Gets or sets the grid line color when drawing the canvas background + /// + [Description("Get or set the grid line color when drawing the canvas background"), DefaultValue(typeof(Color), "Black")] + public Color GridColor { + get { return _GridColor; } + set { + _GridColor = value; + this.Invalidate(); + } + } + + private Color _BorderColor = Color.Black; + /// + /// Gets or sets the border color of the Node in the canvas + /// + [Description("Get or set the border color of Node in the canvas"), DefaultValue(typeof(Color), "Black")] + public Color BorderColor { + get { return _BorderColor; } + set { + _BorderColor = value; + if (m_img_border != null) m_img_border.Dispose(); + m_img_border = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _BorderHoverColor = Color.Gray; + /// + /// Gets or sets the border color of the hovering Node in the canvas + /// + [Description("Get or set the border color of the hovering Node in the canvas"), DefaultValue(typeof(Color), "Gray")] + public Color BorderHoverColor { + get { return _BorderHoverColor; } + set { + _BorderHoverColor = value; + if (m_img_border_hover != null) m_img_border_hover.Dispose(); + m_img_border_hover = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _BorderSelectedColor = Color.Orange; + /// + /// Gets or sets the border color of the selected Node in the canvas + /// + [Description("Get or set the border color of the selected Node in the canvas"), DefaultValue(typeof(Color), "Orange")] + public Color BorderSelectedColor { + get { return _BorderSelectedColor; } + set { + _BorderSelectedColor = value; + if (m_img_border_selected != null) m_img_border_selected.Dispose(); + m_img_border_selected = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _BorderActiveColor = Color.OrangeRed; + /// + /// Gets or sets the border color of the active Node in the canvas + /// + [Description("Get or set the border color of the active Node in the canvas"), DefaultValue(typeof(Color), "OrangeRed")] + public Color BorderActiveColor { + get { return _BorderActiveColor; } + set { + _BorderActiveColor = value; + if (m_img_border_active != null) m_img_border_active.Dispose(); + m_img_border_active = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _MarkForeColor = Color.White; + /// + /// Gets or sets the foreground color used by the canvas to draw the Node tag details + /// + [Description("Get or set the foreground color of the canvas drawing Node tag details"), DefaultValue(typeof(Color), "White")] + public Color MarkForeColor { + get { return _MarkBackColor; } + set { + _MarkBackColor = value; + this.Invalidate(); + } + } + + private Color _MarkBackColor = Color.FromArgb(180, Color.Black); + /// + /// Gets or sets the background color used by the canvas to draw the Node tag details + /// + [Description("Get or set the background color used for drawing Node tag details on canvas")] + public Color MarkBackColor { + get { return _MarkBackColor; } + set { + _MarkBackColor = value; + this.Invalidate(); + } + } + + private Color _MagnetColor = Color.Lime; + /// + /// Gets or sets the color of the magnet marker when moving the Node in the canvas + /// + [Description("Get or set the magnet mark color when moving Node in the canvas"), DefaultValue(typeof(Color), "Lime")] + public Color MagnetColor { + get { return _MagnetColor; } + set { _MagnetColor = value; } + } + + private Color _SelectedRectangleColor = Color.DodgerBlue; + /// + /// Gets or sets the color of the selection rectangle in the canvas + /// + [Description("Get or set the color of the selection rectangle in the canvas"), DefaultValue(typeof(Color), "DodgerBlue")] + public Color SelectedRectangleColor { + get { return _SelectedRectangleColor; } + set { _SelectedRectangleColor = value; } + } + + private Color _HighLineColor = Color.Cyan; + /// + /// Gets or sets the color of the highlighted line in the canvas + /// + [Description("Get or set the color of the highlighted line in the canvas"), DefaultValue(typeof(Color), "Cyan")] + public Color HighLineColor { + get { return _HighLineColor; } + set { _HighLineColor = value; } + } + + private Color _LocationForeColor = Color.Red; + /// + /// Get or set the foreground color of the edge position hint area in the canvas + /// + [Description("Get or set the foreground color of the edge position hint area in the canvas"), DefaultValue(typeof(Color), "Red")] + public Color LocationForeColor { + get { return _LocationForeColor; } + set { + _LocationForeColor = value; + this.Invalidate(); + } + } + + private Color _LocationBackColor = Color.FromArgb(120, Color.Black); + /// + /// Get or set the background color of the edge position hint area in the canvas + /// + [Description("Get or set the background color of the edge position hint area in the canvas")] + public Color LocationBackColor { + get { return _LocationBackColor; } + set { + _LocationBackColor = value; + this.Invalidate(); + } + } + + private Color _UnknownTypeColor = Color.Gray; + /// + /// Gets or sets the color that should be used in the canvas when the Option data type in Node cannot be determined + /// + [Description("Get or set the color that should be used when the Option data type in Node cannot be determined"), DefaultValue(typeof(Color), "Gray")] + public Color UnknownTypeColor { + get { return _UnknownTypeColor; } + set { + _UnknownTypeColor = value; + this.Invalidate(); + } + } + + private Dictionary _TypeColor = new Dictionary(); + /// + /// Get or set the preset color of the Option data type in the Node in the canvas + /// + [Browsable(false)] + public Dictionary TypeColor { + get { return _TypeColor; } + } + + #endregion + + #region protected properties ---------------------------------------------------------------------------------------- + /// + /// The real-time position of the current mouse in the control + /// + protected Point m_pt_in_control; + /// + /// The real-time position of the current mouse in the canvas + /// + protected PointF m_pt_in_canvas; + /// + /// The position on the control when the mouse is clicked + /// + protected Point m_pt_down_in_control; + /// + /// The position in the canvas when the mouse is clicked + /// + protected PointF m_pt_down_in_canvas; + /// + /// Used for the coordinate position of the canvas when the mouse is clicked when the mouse is clicked to move the canvas + /// + protected PointF m_pt_canvas_old; + /// + /// Used to save the starting point coordinates of the Option under the save point during the connection process + /// + protected Point m_pt_dot_down; + /// + /// Used to save the starting point of the mouse click during the connection process. Option When MouseUP determines whether to connect this node + /// + protected STNodeOption m_option_down; + /// + /// STNode under the current mouse click + /// + protected STNode m_node_down; + /// + /// Whether the current mouse is in the control + /// + protected bool m_mouse_in_control; + + #endregion + + public STNodeEditor() { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + this._Nodes = new STNodeCollection(this); + this.BackColor = Color.FromArgb(255, 34, 34, 34); + this.MinimumSize = new Size(100, 100); + this.Size = new Size(200, 200); + this.AllowDrop = true; + + m_real_canvas_x = this._CanvasOffsetX = 10; + m_real_canvas_y = this._CanvasOffsetY = 10; + } + + #region private fields -------------------------------------------------------------------------------------- + + private DrawingTools m_drawing_tools; + private NodeFindInfo m_find = new NodeFindInfo(); + private MagnetInfo m_mi = new MagnetInfo (); + + private RectangleF m_rect_select = new RectangleF(); + //Node border preset pattern + private Image m_img_border; + private Image m_img_border_hover; + private Image m_img_border_selected; + private Image m_img_border_active; + //Used for the animation effect when the mouse scrolls or the touchpad moves the canvas. This value is the real coordinate address that needs to be moved to view ->MoveCanvasThread() + private float m_real_canvas_x; + private float m_real_canvas_y; + //Used to save the initial coordinates of the node selected when the mouse is clicked when moving the node + private Dictionary m_dic_pt_selected = new Dictionary(); + //For the magnet effect to move the node, the statistics of the non-selected nodes need to participate in the coordinate view of the magnet effect ->BuildMagnetLocation() + private List m_lst_magnet_x = new List(); + private List m_lst_magnet_y = new List(); + //Used for the magnet effect to move the node when the active selection node is counted to check the coordinates that need to participate in the magnet effect ->CheckMagnet() + private List m_lst_magnet_mx = new List(); + private List m_lst_magnet_my = new List(); + //Used to calculate the time trigger interval in mouse scrolling. View the displacement generated by different canvases according to the interval -> OnMouseWheel(), OnMouseHWheel() + private DateTime m_dt_vw = DateTime.Now; + private DateTime m_dt_hw = DateTime.Now; + // current behavior during mouse movement + private CanvasAction m_ca; + // save the selected node + private HashSet m_hs_node_selected = new HashSet(); + + private bool m_is_process_mouse_event = true; //Whether to pass down (Node or NodeControls) mouse related events such as disconnection related operations should not pass down + private bool m_is_buildpath; //The path used to determine whether to re-establish the cache connection this time during the redraw process + private Pen m_p_line = new Pen(Color.Cyan, 2f); // used to draw connected lines + private Pen m_p_line_hover = new Pen(Color.Cyan, 4f); //Used to draw the line when the mouse is hovered + private GraphicsPath m_gp_hover; //The path of the current mouse hover + private StringFormat m_sf = new StringFormat(); //The text format is used to set the text format when Mark draws + //Save the node relationship corresponding to each connection line + private Dictionary m_dic_gp_info = new Dictionary(); + //Save the position of Node beyond the visual area + private List m_lst_node_out = new List(); + //The Node type loaded by the current editor is used to load nodes from files or data + private Dictionary m_dic_type = new Dictionary(); + + private int m_time_alert; + private int m_alpha_alert; + private string m_str_alert; + private Color m_forecolor_alert; + private Color m_backcolor_alert; + private DateTime m_dt_alert; + private Rectangle m_rect_alert; + private AlertLocation m_al; + + #endregion + + #region event ---------------------------------------------------------------------------------------------------- + /// + /// Occurs when the active node changes + /// + [Description("Occurs when the active node changes")] + public event EventHandler ActiveChanged; + /// + /// Occurs when the selected node changes + /// + [Description("Occurs when the selected node changes")] + public event EventHandler SelectedChanged; + /// + /// Occurs when the hovered node changes + /// + [Description("Occurs when the hovered node changes")] + public event EventHandler HoverChanged; + /// + /// Occurs when a node is added + /// + [Description("Occurs when a node is added")] + public event STNodeEditorEventHandler NodeAdded; + /// + /// Occurs when the node is removed + /// + [Description("Occurs when a node is removed")] + public event STNodeEditorEventHandler NodeRemoved; + /// + /// Occurs when the origin of the canvas is moved + /// + [Description("Occurs when moving the origin of the canvas")] + public event EventHandler CanvasMoved; + /// + /// Occurs when the canvas is zoomed + /// + [Description("Occurs when zooming the canvas")] + public event EventHandler CanvasScaled; + /// + /// Occurs when connecting node options + /// + [Description("Occurs when connecting node options")] + public event STNodeEditorOptionEventHandler OptionConnected; + /// + /// Occurs when connecting node options + /// + [Description("Occurs when connecting node options")] + public event STNodeEditorOptionEventHandler OptionConnecting; + /// + /// Occurs when the node option is disconnected + /// + [Description("Occurs when the node option is disconnected")] + public event STNodeEditorOptionEventHandler OptionDisConnected; + /// + /// Occurs when disconnecting node options + /// + [Description("Occurs when disconnecting node options")] + public event STNodeEditorOptionEventHandler OptionDisConnecting; + + protected virtual internal void OnSelectedChanged(EventArgs e) { + if (this.SelectedChanged != null) this.SelectedChanged(this, e); + } + protected virtual void OnActiveChanged(EventArgs e) { + if (this.ActiveChanged != null) this.ActiveChanged(this, e); + } + protected virtual void OnHoverChanged(EventArgs e) { + if (this.HoverChanged != null) this.HoverChanged(this, e); + } + protected internal virtual void OnNodeAdded(STNodeEditorEventArgs e) { + if (this.NodeAdded != null) this.NodeAdded(this, e); + } + protected internal virtual void OnNodeRemoved(STNodeEditorEventArgs e) { + if (this.NodeRemoved != null) this.NodeRemoved(this, e); + } + protected virtual void OnCanvasMoved(EventArgs e) { + if (this.CanvasMoved != null) this.CanvasMoved(this, e); + } + protected virtual void OnCanvasScaled(EventArgs e) { + if (this.CanvasScaled != null) this.CanvasScaled(this, e); + } + protected internal virtual void OnOptionConnected(STNodeEditorOptionEventArgs e) { + if (this.OptionConnected != null) this.OptionConnected(this, e); + } + protected internal virtual void OnOptionDisConnected(STNodeEditorOptionEventArgs e) { + if (this.OptionDisConnected != null) this.OptionDisConnected(this, e); + } + protected internal virtual void OnOptionConnecting(STNodeEditorOptionEventArgs e) { + if (this.OptionConnecting != null) this.OptionConnecting(this, e); + } + protected internal virtual void OnOptionDisConnecting(STNodeEditorOptionEventArgs e) { + if (this.OptionDisConnecting != null) this.OptionDisConnecting(this, e); + } + + #endregion event + + #region override ----------------------------------------------------------------------------------------------------- + + protected override void OnCreateControl() { + m_drawing_tools = new DrawingTools() { + Pen = new Pen(Color.Black, 1), + SolidBrush = new SolidBrush(Color.Black) + }; + m_img_border = this.CreateBorderImage(this._BorderColor); + m_img_border_active = this.CreateBorderImage(this._BorderActiveColor); + m_img_border_hover = this.CreateBorderImage(this._BorderHoverColor); + m_img_border_selected = this.CreateBorderImage(this._BorderSelectedColor); + base.OnCreateControl(); + new Thread(this.MoveCanvasThread) { IsBackground = true }.Start(); + new Thread(this.ShowAlertThread) { IsBackground = true }.Start(); + m_sf = new StringFormat(); + m_sf.Alignment = StringAlignment.Near; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + m_sf.SetTabStops(0, new float[] { 40 }); + } + + protected override void WndProc(ref Message m) { + base.WndProc(ref m); + try { + Point pt = new Point(((int)m.LParam) >> 16, (ushort)m.LParam); + pt = this.PointToClient(pt); + if (m.Msg == WM_MOUSEHWHEEL) { //Get the horizontal scrolling message + MouseButtons mb = MouseButtons.None; + int n = (ushort)m.WParam; + if ((n & 0x0001) == 0x0001) mb |= MouseButtons.Left; + if ((n & 0x0010) == 0x0010) mb |= MouseButtons.Middle; + if ((n & 0x0002) == 0x0002) mb |= MouseButtons.Right; + if ((n & 0x0020) == 0x0020) mb |= MouseButtons.XButton1; + if ((n & 0x0040) == 0x0040) mb |= MouseButtons.XButton2; + this.OnMouseHWheel(new MouseEventArgs(mb, 0, pt.X, pt.Y, ((int)m.WParam) >> 16)); + } + } catch { /*add code*/ } + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + g.Clear(this.BackColor); + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + m_drawing_tools.Graphics = g; + SolidBrush brush = m_drawing_tools.SolidBrush; + + if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, this.Width, this.Height); + + g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //Move the coordinate system + g.ScaleTransform(this._CanvasScale, this._CanvasScale); //Scale the drawing surface + + this.OnDrawConnectedLine(m_drawing_tools); + this.OnDrawNode(m_drawing_tools, this.ControlToCanvas(this.ClientRectangle)); + + if (m_ca == CanvasAction.ConnectOption) { //If connecting + m_drawing_tools.Pen.Color = this._HighLineColor; + g.SmoothingMode = SmoothingMode.HighQuality; + if (m_option_down.IsInput) + this.DrawBezier(g, m_drawing_tools.Pen, m_pt_in_canvas, m_pt_dot_down, this._Curvature); + else + this.DrawBezier(g, m_drawing_tools.Pen, m_pt_dot_down, m_pt_in_canvas, this._Curvature); + } + //Reset the drawing coordinates I think other decoration-related drawing other than nodes should not be drawn in the Canvas coordinate system but should be drawn using the coordinates of the control, otherwise it will be affected by the zoom ratio + g.ResetTransform(); + + switch (m_ca) { + case CanvasAction.MoveNode: //Draw alignment guides during movement + if (this._ShowMagnet && this._ActiveNode != null) this.OnDrawMagnet(m_drawing_tools, m_mi); + break; + case CanvasAction.SelectRectangle: //Draw rectangle selection + this.OnDrawSelectedRectangle(m_drawing_tools, this.CanvasToControl(m_rect_select)); + break; + case CanvasAction.DrawMarkDetails: //Draw mark information details + if (!string.IsNullOrEmpty(m_find.Mark)) this.OnDrawMark(m_drawing_tools); + break; + } + + if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, this.Size, m_lst_node_out); + this.OnDrawAlert(g); + } + + protected override void OnMouseDown(MouseEventArgs e) { + base.OnMouseDown(e); + this.Focus(); + m_ca = CanvasAction.None; + m_mi.XMatched = m_mi.YMatched = false; + m_pt_down_in_control = e.Location; + m_pt_down_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale); + m_pt_down_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale); + m_pt_canvas_old.X = this._CanvasOffsetX; + m_pt_canvas_old.Y = this._CanvasOffsetY; + + if (m_gp_hover != null && e.Button == MouseButtons.Right) { //Disconnect + this.DisConnectionHover(); + m_is_process_mouse_event = false; // Terminate the downward transmission of MouseClick and MouseUp + return; + } + + NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_down_in_canvas); + if (!string.IsNullOrEmpty(nfi.Mark)) { //If the point is marked information + m_ca = CanvasAction.DrawMarkDetails; + this.Invalidate(); + return; + } + + if (nfi.NodeOption != null) { //If the connection point of the Option under the point + this.StartConnect(nfi.NodeOption); + return; + } + + if (nfi.Node != null) { + nfi.Node.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, (int)m_pt_down_in_canvas.X - nfi.Node.Left, (int)m_pt_down_in_canvas.Y - nfi.Node.Top, e.Delta)); + bool bCtrlDown = (Control.ModifierKeys & Keys.Control) == Keys.Control; + if (bCtrlDown) { + if (nfi.Node.IsSelected) { + if (nfi.Node == this._ActiveNode) { + this.SetActiveNode(null); + } + } else { + nfi.Node.SetSelected(true, true); + } + return; + } else if (!nfi.Node.IsSelected) { + foreach (var n in m_hs_node_selected.ToArray()) n.SetSelected(false, false); + } + nfi.Node.SetSelected(true, false); //Add to the selected node + this.SetActiveNode(nfi.Node); + if (this.PointInRectangle(nfi.Node.TitleRectangle, m_pt_down_in_canvas.X, m_pt_down_in_canvas.Y)) { + if (e.Button == MouseButtons.Right) { + if (nfi.Node.ContextMenuStrip != null) { + nfi.Node.ContextMenuStrip.Show(this.PointToScreen(e.Location)); + } + } else { + m_dic_pt_selected.Clear(); + lock (m_hs_node_selected) { + foreach (STNode n in m_hs_node_selected) //Record the position of the selected node, which will be useful if you need to move the selected node + m_dic_pt_selected.Add(n, n.Location); + } + m_ca = CanvasAction.MoveNode; //If the title of the node is under the point, the node can be moved + if (this._ShowMagnet && this._ActiveNode != null) this.BuildMagnetLocation(); //The coordinates needed to build the magnet will be useful if you need to move the selected node + } + } else + m_node_down = nfi.Node; + } else { + this.SetActiveNode(null); + foreach (var n in m_hs_node_selected.ToArray()) n.SetSelected(false, false);//Empty the selected node without clicking anything + m_ca = CanvasAction.SelectRectangle; //Enter rectangular area selection mode + m_rect_select.Width = m_rect_select.Height = 0; + m_node_down = null; + } + //this.SetActiveNode(nfi.Node); + } + + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + m_pt_in_control = e.Location; + m_pt_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale); + m_pt_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale); + + if (m_node_down != null) { + m_node_down.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - m_node_down.Left, + (int)m_pt_in_canvas.Y - m_node_down.Top, e.Delta)); + return; + } + + if (e.Button == MouseButtons.Middle) { //The middle mouse button moves the canvas + this._CanvasOffsetX = m_real_canvas_x = m_pt_canvas_old.X + (e.X - m_pt_down_in_control.X); + this._CanvasOffsetY = m_real_canvas_y = m_pt_canvas_old.Y + (e.Y - m_pt_down_in_control.Y); + this.Invalidate(); + return; + } + if (e.Button == MouseButtons.Left) { //If the left mouse button is clicked to judge the behavior + m_gp_hover = null; + switch (m_ca) { + case CanvasAction.MoveNode: this.MoveNode(e.Location); return; //The current moving node + case CanvasAction.ConnectOption: this.Invalidate(); return; //Currently connecting + case CanvasAction.SelectRectangle: //Currently being selected + m_rect_select.X = m_pt_down_in_canvas.X < m_pt_in_canvas.X ? m_pt_down_in_canvas.X : m_pt_in_canvas.X; + m_rect_select.Y = m_pt_down_in_canvas.Y < m_pt_in_canvas.Y ? m_pt_down_in_canvas.Y : m_pt_in_canvas.Y; + m_rect_select.Width = Math.Abs(m_pt_in_canvas.X - m_pt_down_in_canvas.X); + m_rect_select.Height = Math.Abs(m_pt_in_canvas.Y - m_pt_down_in_canvas.Y); + foreach (STNode n in this._Nodes) { + n.SetSelected(m_rect_select.IntersectsWith(n.Rectangle), false); + } + this.Invalidate(); + return; + } + } + //If there is no behavior, determine whether there are other objects under the mouse + NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_in_canvas); + bool bRedraw = false; + if (this._HoverNode != nfi.Node) { //Mouse over Node + if (nfi.Node != null) nfi.Node.OnMouseEnter(EventArgs.Empty); + if (this._HoverNode != null) + this._HoverNode.OnMouseLeave(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + this._HoverNode = nfi.Node; + this.OnHoverChanged(EventArgs.Empty); + bRedraw = true; + } + if (this._HoverNode != null) { + this._HoverNode.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + m_gp_hover = null; + } else { + GraphicsPath gp = null; + foreach (var v in m_dic_gp_info) { //Determine whether the mouse hovers over the connection path + if (v.Key.IsOutlineVisible(m_pt_in_canvas, m_p_line_hover)) { + gp = v.Key; + break; + } + } + if (m_gp_hover != gp) { + m_gp_hover = gp; + bRedraw = true; + } + } + if (bRedraw) this.Invalidate(); + } + + protected override void OnMouseUp(MouseEventArgs e) { + base.OnMouseUp(e); + var nfi = this.FindNodeFromPoint(m_pt_in_canvas); + switch (m_ca) { //Judgment behavior when the mouse is raised + case CanvasAction.MoveNode: //If the Node is being moved, re-record the current position + foreach (STNode n in m_dic_pt_selected.Keys.ToList()) m_dic_pt_selected[n] = n.Location; + break; + case CanvasAction.ConnectOption: //If it is connecting, end the connection + if (e.Location == m_pt_down_in_control) break; + if (nfi.NodeOption != null) { + if (m_option_down.IsInput) + nfi.NodeOption.ConnectOption(m_option_down); + else + m_option_down.ConnectOption(nfi.NodeOption); + } + break; + } + if (m_is_process_mouse_event && this._ActiveNode != null) { + var mea = new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._ActiveNode.Left, + (int)m_pt_in_canvas.Y - this._ActiveNode.Top, e.Delta); + this._ActiveNode.OnMouseUp(mea); + m_node_down = null; + } + m_is_process_mouse_event = true; //Currently, no event delivery is performed for the disconnection operation, and the event will be accepted next time + m_ca = CanvasAction.None; + this.Invalidate(); + } + + protected override void OnMouseEnter(EventArgs e) { + base.OnMouseEnter(e); + m_mouse_in_control = true; + } + + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + m_mouse_in_control = false; + if (this._HoverNode != null) this._HoverNode.OnMouseLeave(e); + this._HoverNode = null; + this.Invalidate(); + } + + protected override void OnMouseWheel(MouseEventArgs e) { + base.OnMouseWheel(e); + if ((Control.ModifierKeys & Keys.Control) == Keys.Control) { + float f = this._CanvasScale + (e.Delta < 0 ? -0.1f : 0.1f); + this.ScaleCanvas(f, this.Width / 2, this.Height / 2); + } else { + if (!m_mouse_in_control) return; + var nfi = this.FindNodeFromPoint(m_pt_in_canvas); + if (this._HoverNode != null) { + this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + return; + } + int t = (int)DateTime.Now.Subtract(m_dt_vw).TotalMilliseconds; + if (t <= 30) t = 40; + else if (t <= 100) t = 20; + else if (t <= 150) t = 10; + else if (t <= 300) t = 4; + else t = 2; + this.MoveCanvas(this._CanvasOffsetX, m_real_canvas_y + (e.Delta < 0 ? -t : t), true, CanvasMoveArgs.Top);//process mouse mid + m_dt_vw = DateTime.Now; + } + } + + protected virtual void OnMouseHWheel(MouseEventArgs e) { + if ((Control.ModifierKeys & Keys.Control) == Keys.Control) return; + if (!m_mouse_in_control) return; + if (this._HoverNode != null) { + this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + return; + } + int t = (int)DateTime.Now.Subtract(m_dt_hw).TotalMilliseconds; + if (t <= 30) t = 40; + else if (t <= 100) t = 20; + else if (t <= 150) t = 10; + else if (t <= 300) t = 4; + else t = 2; + this.MoveCanvas(m_real_canvas_x + (e.Delta > 0 ? -t : t), this._CanvasOffsetY, true, CanvasMoveArgs.Left); + m_dt_hw = DateTime.Now; + } + //===========================for node other event================================== + protected override void OnMouseClick(MouseEventArgs e) { + base.OnMouseClick(e); + if (this._ActiveNode != null && m_is_process_mouse_event) { + if (!this.PointInRectangle(this._ActiveNode.Rectangle, m_pt_in_canvas.X, m_pt_in_canvas.Y)) return; + this._ActiveNode.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_down_in_canvas.X - this._ActiveNode.Left, + (int)m_pt_down_in_canvas.Y - this._ActiveNode.Top, e.Delta)); + } + } + + protected override void OnKeyDown(KeyEventArgs e) { + base.OnKeyDown(e); + if (this._ActiveNode != null) this._ActiveNode.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) { + base.OnKeyUp(e); + if (this._ActiveNode != null) this._ActiveNode.OnKeyUp(e); + m_node_down = null; + } + + protected override void OnKeyPress(KeyPressEventArgs e) { + base.OnKeyPress(e); + if (this._ActiveNode != null) this._ActiveNode.OnKeyPress(e); + } + + #endregion + + protected override void OnDragEnter(DragEventArgs drgevent) { + base.OnDragEnter(drgevent); + if (this.DesignMode) return; + if (drgevent.Data.GetDataPresent("STNodeType")) + drgevent.Effect = DragDropEffects.Copy; + else + drgevent.Effect = DragDropEffects.None; + + } + + protected override void OnDragDrop(DragEventArgs drgevent) { + base.OnDragDrop(drgevent); + if (this.DesignMode) return; + if (drgevent.Data.GetDataPresent("STNodeType")) { + object data = drgevent.Data.GetData("STNodeType"); + if (!(data is Type)) return; + var t = (Type)data; + if (!t.IsSubclassOf(typeof(STNode))) return; + STNode node = (STNode)Activator.CreateInstance((t)); + Point pt = new Point(drgevent.X, drgevent.Y); + pt = this.PointToClient(pt); + pt = this.ControlToCanvas(pt); + node.Left = pt.X; node.Top = pt.Y; + this.Nodes.Add(node); + } + } + + #region protected ---------------------------------------------------------------------------------------------------- + /// + /// Occurs when the background grid lines are drawn + /// + /// Drawing tool + /// Need to draw width + /// Need to draw height + protected virtual void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight) { + Graphics g = dt.Graphics; + using (Pen p_2 = new Pen(Color.FromArgb(65, this._GridColor))) { + using (Pen p_1 = new Pen(Color.FromArgb(30, this._GridColor))) { + float nIncrement = (20 * this._CanvasScale); //The interval between grids is drawn according to the scale + int n = 5 - (int)(this._CanvasOffsetX / nIncrement); + for (float f = this._CanvasOffsetX % nIncrement; f < nWidth; f += nIncrement) + g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), f, 0, f, nHeight); + n = 5 - (int)(this._CanvasOffsetY / nIncrement); + for (float f = this._CanvasOffsetY % nIncrement; f < nHeight; f += nIncrement) + g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), 0, f, nWidth, f); + // two antennas at the origin + p_1.Color = Color.FromArgb(this._Nodes.Count == 0 ? 255 : 120, this._GridColor); + g.DrawLine(p_1, this._CanvasOffsetX, 0, this._CanvasOffsetX, nHeight); + g.DrawLine(p_1, 0, this._CanvasOffsetY, nWidth, this._CanvasOffsetY); + } + } + } + /// + /// Occurs when the Node is drawn + /// + /// Drawing tool + /// Visible canvas area size + protected virtual void OnDrawNode(DrawingTools dt, Rectangle rect) { + m_lst_node_out.Clear(); //Clear the coordinates of Node beyond the visual area + foreach (STNode n in this._Nodes) { + if (this._ShowBorder) this.OnDrawNodeBorder(dt, n); + n.OnDrawNode(dt); //Call Node to draw the main part by itself + if (!string.IsNullOrEmpty(n.Mark)) n.OnDrawMark(dt); //Call Node to draw the Mark area by itself + if (!rect.IntersectsWith(n.Rectangle)) { + m_lst_node_out.Add(n.Location); //Determine whether this Node is beyond the visual area + } + } + } + /// + /// Occurs when the Node border is drawn + /// + /// Drawing tool + /// Target node + protected virtual void OnDrawNodeBorder(DrawingTools dt, STNode node) { + Image img_border = null; + if (this._ActiveNode == node) img_border = m_img_border_active; + else if (node.IsSelected) img_border = m_img_border_selected; + else if (this._HoverNode == node) img_border = m_img_border_hover; + else img_border = m_img_border; + this.RenderBorder(dt.Graphics, node.Rectangle, img_border); + if (!string.IsNullOrEmpty(node.Mark)) this.RenderBorder(dt.Graphics, node.MarkRectangle, img_border); + } + /// + /// Occurs when a connected path is drawn + /// + /// Drawing tool + protected virtual void OnDrawConnectedLine(DrawingTools dt) { + Graphics g = dt.Graphics; + g.SmoothingMode = SmoothingMode.HighQuality; + m_p_line_hover.Color = Color.FromArgb(50, 0, 0, 0); + var t = typeof(object); + foreach (STNode n in this._Nodes) { + foreach (STNodeOption op in n.OutputOptions) { + if (op == STNodeOption.Empty) continue; + if (op.DotColor != Color.Transparent) //Determine the line color + m_p_line.Color = op.DotColor; + else { + if (op.DataType == t) + m_p_line.Color = this._UnknownTypeColor; + else + m_p_line.Color = this._TypeColor.ContainsKey(op.DataType) ? this._TypeColor[op.DataType] : this._UnknownTypeColor;//value can not be null + } + foreach (var v in op.ConnectedOption) { + this.DrawBezier(g, m_p_line_hover, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + this.DrawBezier(g, m_p_line, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + if (m_is_buildpath) { //If the current drawing needs to re-establish the connected path cache + GraphicsPath gp = this.CreateBezierPath(op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + m_dic_gp_info.Add(gp, new ConnectionInfo() { Output = op, Input = v }); + } + } + } + } + m_p_line_hover.Color = this._HighLineColor; + if (m_gp_hover != null) { //If there is currently a hovered connection road, it will be highlighted and drawn + g.DrawPath(m_p_line_hover, m_gp_hover); + } + m_is_buildpath = false; //The reset flag will not re-build the path cache next time you draw + } + /// + /// Occurs when drawing Mark details + /// + /// Drawing tool + protected virtual void OnDrawMark(DrawingTools dt) { + Graphics g = dt.Graphics; + SizeF sz = g.MeasureString(m_find.Mark, this.Font); //Confirm the required size of the text + Rectangle rect = new Rectangle(m_pt_in_control.X + 15, + m_pt_in_control.Y + 10, + (int)sz.Width + 6, + 4 + (this.Font.Height + 4) * m_find.MarkLines.Length); //sz.Height does not consider the line spacing of the text, so the height is calculated by itself + + if (rect.Right > this.Width) rect.X = this.Width - rect.Width; + if (rect.Bottom > this.Height) rect.Y = this.Height - rect.Height; + if (rect.X < 0) rect.X = 0; + if (rect.Y < 0) rect.Y = 0; + + dt.SolidBrush.Color = this._MarkBackColor; + g.SmoothingMode = SmoothingMode.None; + g.FillRectangle(dt.SolidBrush, rect); //Draw the background area + rect.Width--; rect.Height--; + dt.Pen.Color = Color.FromArgb(255, this._MarkBackColor); + g.DrawRectangle(dt.Pen, rect); + dt.SolidBrush.Color = this._MarkForeColor; + + m_sf.LineAlignment = StringAlignment.Center; + //g.SmoothingMode = SmoothingMode.HighQuality; + rect.X += 2; rect.Width -= 3; + rect.Height = this.Font.Height + 4; + int nY = rect.Y + 2; + for (int i = 0; i < m_find.MarkLines.Length; i++) { //draw text + rect.Y = nY + i * (this.Font.Height + 4); + g.DrawString(m_find.MarkLines[i], this.Font, dt.SolidBrush, rect, m_sf); + } + } + /// + /// Occurs when the alignment guide needs to be displayed when moving the Node + /// + /// Drawing tool + /// Matching magnet information + protected virtual void OnDrawMagnet(DrawingTools dt, MagnetInfo mi) { + if (this._ActiveNode == null) return; + Graphics g = dt.Graphics; + Pen pen = m_drawing_tools.Pen; + SolidBrush brush = dt.SolidBrush; + pen.Color = this._MagnetColor; + brush.Color = Color.FromArgb(this._MagnetColor.A / 3, this._MagnetColor); + g.SmoothingMode = SmoothingMode.None; + int nL = this._ActiveNode.Left, nMX = this._ActiveNode.Left + this._ActiveNode.Width / 2, nR = this._ActiveNode.Right; + int nT = this._ActiveNode.Top, nMY = this._ActiveNode.Top + this._ActiveNode.Height / 2, nB = this._ActiveNode.Bottom; + if (mi.XMatched) g.DrawLine(pen, this.CanvasToControl(mi.X, true), 0, this.CanvasToControl(mi.X, true), this.Height); + if (mi.YMatched) g.DrawLine(pen, 0, this.CanvasToControl(mi.Y, false), this.Width, this.CanvasToControl(mi.Y, false)); + g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //Move the coordinate system + g.ScaleTransform(this._CanvasScale, this._CanvasScale); //Scale the drawing surface + if (mi.XMatched) { + //g.DrawLine(pen, this.CanvasToControl(mi.X, true), 0, this.CanvasToControl(mi.X, true), this.Height); + foreach (STNode n in this._Nodes) { + if (n.Left == mi.X || n.Right == mi.X || n.Left + n.Width / 2 == mi.X) { + //g.DrawRectangle(pen, n.Left, n.Top, n.Width - 1, n.Height - 1); + g.FillRectangle(brush, n.Rectangle); + } + } + } + if (mi.YMatched) { + //g.DrawLine(pen, 0, this.CanvasToControl(mi.Y, false), this.Width, this.CanvasToControl(mi.Y, false)); + foreach (STNode n in this._Nodes) { + if (n.Top == mi.Y || n.Bottom == mi.Y || n.Top + n.Height / 2 == mi.Y) { + //g.DrawRectangle(pen, n.Left, n.Top, n.Width - 1, n.Height - 1); + g.FillRectangle(brush, n.Rectangle); + } + } + } + g.ResetTransform(); + } + /// + /// Draw the selected rectangular area + /// + /// Drawing tool + /// Rectangular area on the control + protected virtual void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + dt.Pen.Color = this._SelectedRectangleColor; + g.DrawRectangle(dt.Pen, rectf.Left, rectf.Y, rectf.Width, rectf.Height); + brush.Color = Color.FromArgb(this._SelectedRectangleColor.A / 3, this._SelectedRectangleColor); + g.FillRectangle(brush, this.CanvasToControl(m_rect_select)); + } + /// + /// Draw the Node position prompt information beyond the visual area + /// + /// Drawing tool + /// Prompt box margin + /// Node position information beyond the visual area + protected virtual void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List lstPts) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + brush.Color = this._LocationBackColor; + g.SmoothingMode = SmoothingMode.None; + if (lstPts.Count == this._Nodes.Count && this._Nodes.Count != 0) { //If the number of excesses is the same as the number of sets, all of them will exceed the drawing circumscribed rectangle + g.FillRectangle(brush, this.CanvasToControl(this._CanvasValidBounds)); + } + g.FillRectangle(brush, 0, 0, 4, sz.Height); //Draw the four-sided background + g.FillRectangle(brush, sz.Width - 4, 0, 4, sz.Height); + g.FillRectangle(brush, 4, 0, sz.Width - 8, 4); + g.FillRectangle(brush, 4, sz.Height - 4, sz.Width - 8, 4); + brush.Color = this._LocationForeColor; + foreach (var v in lstPts) { // draw points + var pt = this.CanvasToControl(v); + if (pt.X < 0) pt.X = 0; + if (pt.Y < 0) pt.Y = 0; + if (pt.X > sz.Width) pt.X = sz.Width - 4; + if (pt.Y > sz.Height) pt.Y = sz.Height - 4; + g.FillRectangle(brush, pt.X, pt.Y, 4, 4); + } + } + /// + /// Drawing prompt information + /// + /// Drawing tool + /// Area to be drawn + /// Need to draw text + /// Information foreground color + /// Information background color + /// Information Location + protected virtual void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al) { + if (m_alpha_alert == 0) return; + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + + g.SmoothingMode = SmoothingMode.None; + brush.Color = backColor; + dt.Pen.Color = brush.Color; + g.FillRectangle(brush, rect); + g.DrawRectangle(dt.Pen, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1); + + brush.Color = foreColor; + m_sf.Alignment = StringAlignment.Center; + m_sf.LineAlignment = StringAlignment.Center; + g.SmoothingMode = SmoothingMode.HighQuality; + g.DrawString(strText, this.Font, brush, rect, m_sf); + } + /// + /// Get the rectangular area that needs to be drawn for the prompt information + /// + /// Drawing surface + /// Need to draw text + /// Information Location + /// Rectangular area + protected virtual Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al) { + SizeF szf = g.MeasureString(m_str_alert, this.Font); + Size sz = new Size((int)Math.Round(szf.Width + 10), (int)Math.Round(szf.Height + 4)); + Rectangle rect = new Rectangle(4, this.Height - sz.Height - 4, sz.Width, sz.Height); + + switch (al) { + case AlertLocation.Left: + rect.Y = (this.Height - sz.Height) >> 1; + break; + case AlertLocation.Top: + rect.Y = 4; + rect.X = (this.Width - sz.Width) >> 1; + break; + case AlertLocation.Right: + rect.X = this.Width - sz.Width - 4; + rect.Y = (this.Height - sz.Height) >> 1; + break; + case AlertLocation.Bottom: + rect.X = (this.Width - sz.Width) >> 1; + break; + case AlertLocation.Center: + rect.X = (this.Width - sz.Width) >> 1; + rect.Y = (this.Height - sz.Height) >> 1; + break; + case AlertLocation.LeftTop: + rect.X = rect.Y = 4; + break; + case AlertLocation.RightTop: + rect.Y = 4; + rect.X = this.Width - sz.Width - 4; + break; + case AlertLocation.RightBottom: + rect.X = this.Width - sz.Width - 4; + break; + } + return rect; + } + + #endregion protected + + #region internal + + internal void BuildLinePath() { + foreach (var v in m_dic_gp_info) v.Key.Dispose(); + m_dic_gp_info.Clear(); + m_is_buildpath = true; + this.Invalidate(); + } + + internal void OnDrawAlert(Graphics g) { + m_rect_alert = this.GetAlertRectangle(g, m_str_alert, m_al); + Color clr_fore = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_forecolor_alert.A), m_forecolor_alert); + Color clr_back = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_backcolor_alert.A), m_backcolor_alert); + this.OnDrawAlert(m_drawing_tools, m_rect_alert, m_str_alert, clr_fore, clr_back, m_al); + } + + internal void InternalAddSelectedNode(STNode node) { + node.IsSelected = true; + lock (m_hs_node_selected) m_hs_node_selected.Add(node); + } + + internal void InternalRemoveSelectedNode(STNode node) { + node.IsSelected = false; + lock (m_hs_node_selected) m_hs_node_selected.Remove(node); + } + + #endregion internal + + #region private ----------------------------------------------------------------------------------------------------- + + private void MoveCanvasThread() { + bool bRedraw; + while (true) { + bRedraw = false; + if (m_real_canvas_x != this._CanvasOffsetX) { + float nx = m_real_canvas_x - this._CanvasOffsetX; + float n = Math.Abs(nx) / 10; + float nTemp = Math.Abs(nx); + if (nTemp <= 4) n = 1; + else if (nTemp <= 12) n = 2; + else if (nTemp <= 30) n = 3; + if (nTemp < 1) this._CanvasOffsetX = m_real_canvas_x; + else + this._CanvasOffsetX += nx > 0 ? n : -n; + bRedraw = true; + } + if (m_real_canvas_y != this._CanvasOffsetY) { + float ny = m_real_canvas_y - this._CanvasOffsetY; + float n = Math.Abs(ny) / 10; + float nTemp = Math.Abs(ny); + if (nTemp <= 4) n = 1; + else if (nTemp <= 12) n = 2; + else if (nTemp <= 30) n = 3; + if (nTemp < 1) + this._CanvasOffsetY = m_real_canvas_y; + else + this._CanvasOffsetY += ny > 0 ? n : -n; + bRedraw = true; + } + if (bRedraw) { + m_pt_canvas_old.X = this._CanvasOffsetX; + m_pt_canvas_old.Y = this._CanvasOffsetY; + this.Invalidate(); + Thread.Sleep(30); + } else { + Thread.Sleep(100); + } + } + } + + private void ShowAlertThread() { + while (true) { + int nTime = m_time_alert - (int)DateTime.Now.Subtract(m_dt_alert).TotalMilliseconds; + if (nTime > 0) { + Thread.Sleep(nTime); + continue; + } + if (nTime < -1000) { + if (m_alpha_alert != 0) { + m_alpha_alert = 0; + this.Invalidate(); + } + Thread.Sleep(100); + } else { + m_alpha_alert = (int)(255 - (-nTime / 1000F) * 255); + this.Invalidate(m_rect_alert); + Thread.Sleep(50); + } + } + } + + private Image CreateBorderImage(Color clr) { + Image img = new Bitmap(12, 12); + using (Graphics g = Graphics.FromImage(img)) { + g.SmoothingMode = SmoothingMode.HighQuality; + using (GraphicsPath gp = new GraphicsPath()) { + gp.AddEllipse(new Rectangle(0, 0, 11, 11)); + using (PathGradientBrush b = new PathGradientBrush(gp)) { + b.CenterColor = Color.FromArgb(200, clr); + b.SurroundColors = new Color[] { Color.FromArgb(10, clr) }; + g.FillPath(b, gp); + } + } + } + return img; + } + + private ConnectionStatus DisConnectionHover() { + if (!m_dic_gp_info.ContainsKey(m_gp_hover)) return ConnectionStatus.DisConnected; + ConnectionInfo ci = m_dic_gp_info[m_gp_hover]; + var ret = ci.Output.DisConnectOption(ci.Input); + //this.OnOptionDisConnected(new STNodeOptionEventArgs(ci.Output, ci.Input, ret)); + if (ret == ConnectionStatus.DisConnected) { + m_dic_gp_info.Remove(m_gp_hover); + m_gp_hover.Dispose(); + m_gp_hover = null; + this.Invalidate(); + } + return ret; + } + + private void StartConnect(STNodeOption op) { + if (op.IsInput) { + m_pt_dot_down.X = op.DotLeft; + m_pt_dot_down.Y = op.DotTop + 5; + } else { + m_pt_dot_down.X = op.DotLeft + op.DotSize; + m_pt_dot_down.Y = op.DotTop + 5; + } + m_ca = CanvasAction.ConnectOption; + m_option_down = op; + } + + private void MoveNode(Point pt) { + int nX = (int)((pt.X - m_pt_down_in_control.X) / this._CanvasScale); + int nY = (int)((pt.Y - m_pt_down_in_control.Y) / this._CanvasScale); + lock (m_hs_node_selected) { + foreach (STNode v in m_hs_node_selected) { + v.Left = m_dic_pt_selected[v].X + nX; + v.Top = m_dic_pt_selected[v].Y + nY; + } + if (this._ShowMagnet) { + MagnetInfo mi = this.CheckMagnet(this._ActiveNode); + if (mi.XMatched) { + foreach (STNode v in m_hs_node_selected) v.Left -= mi.OffsetX; + } + if (mi.YMatched) { + foreach (STNode v in m_hs_node_selected) v.Top -= mi.OffsetY; + } + } + } + this.Invalidate(); + } + + protected internal virtual void BuildBounds() { + if (this._Nodes.Count == 0) { + this._CanvasValidBounds = this.ControlToCanvas(this.DisplayRectangle); + return; + } + int x = int.MaxValue; + int y = int.MaxValue; + int r = int.MinValue; + int b = int.MinValue; + foreach (STNode n in this._Nodes) { + if (x > n.Left) x = n.Left; + if (y > n.Top) y = n.Top; + if (r < n.Right) r = n.Right; + if (b < n.Bottom) b = n.Bottom; + } + this._CanvasValidBounds.X = x - 60; + this._CanvasValidBounds.Y = y - 60; + this._CanvasValidBounds.Width = r - x + 120; + this._CanvasValidBounds.Height = b - y + 120; + } + + private bool PointInRectangle(Rectangle rect, float x, float y) { + if (x < rect.Left) return false; + if (x > rect.Right) return false; + if (y < rect.Top) return false; + if (y > rect.Bottom) return false; + return true; + } + + private void BuildMagnetLocation() { + m_lst_magnet_x.Clear(); + m_lst_magnet_y.Clear(); + foreach (STNode v in this._Nodes) { + if (v.IsSelected) continue; + m_lst_magnet_x.Add(v.Left); + m_lst_magnet_x.Add(v.Left + v.Width / 2); + m_lst_magnet_x.Add(v.Left + v.Width); + m_lst_magnet_y.Add(v.Top); + m_lst_magnet_y.Add(v.Top + v.Height / 2); + m_lst_magnet_y.Add(v.Top + v.Height); + } + } + + private MagnetInfo CheckMagnet(STNode node) { + m_mi.XMatched = m_mi.YMatched = false; + m_lst_magnet_mx.Clear(); + m_lst_magnet_my.Clear(); + m_lst_magnet_mx.Add(node.Left + node.Width / 2); + m_lst_magnet_mx.Add(node.Left); + m_lst_magnet_mx.Add(node.Left + node.Width); + m_lst_magnet_my.Add(node.Top + node.Height / 2); + m_lst_magnet_my.Add(node.Top); + m_lst_magnet_my.Add(node.Top + node.Height); + + bool bFlag = false; + foreach (var mx in m_lst_magnet_mx) { + foreach (var x in m_lst_magnet_x) { + if (Math.Abs(mx - x) <= 5) { + bFlag = true; + m_mi.X = x; + m_mi.OffsetX = mx - x; + m_mi.XMatched = true; + break; + } + } + if (bFlag) break; + } + bFlag = false; + foreach (var my in m_lst_magnet_my) { + foreach (var y in m_lst_magnet_y) { + if (Math.Abs(my - y) <= 5) { + bFlag = true; + m_mi.Y = y; + m_mi.OffsetY = my - y; + m_mi.YMatched = true; + break; + } + } + if (bFlag) break; + } + return m_mi; + } + + private void DrawBezier(Graphics g, Pen p, PointF ptStart, PointF ptEnd, float f) { + this.DrawBezier(g, p, ptStart.X, ptStart.Y, ptEnd.X, ptEnd.Y, f); + } + + private void DrawBezier(Graphics g, Pen p, float x1, float y1, float x2, float y2, float f) { + float n = (Math.Abs(x1 - x2) * f); + if (this._Curvature != 0 && n < 30) n = 30; + g.DrawBezier(p, + x1, y1, + x1 + n, y1, + x2 - n, y2, + x2, y2); + } + + private GraphicsPath CreateBezierPath(float x1, float y1, float x2, float y2, float f) { + GraphicsPath gp = new GraphicsPath(); + float n = (Math.Abs(x1 - x2) * f); + if (this._Curvature != 0 && n < 30) n = 30; + gp.AddBezier( + x1, y1, + x1 + n, y1, + x2 - n, y2, + x2, y2 + ); + return gp; + } + + private void RenderBorder(Graphics g, Rectangle rect, Image img) { + //fill the four corners + g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y - 5, 5, 5), + new Rectangle(0, 0, 5, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.Right, rect.Y - 5, 5, 5), + new Rectangle(img.Width - 5, 0, 5, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.X - 5, rect.Bottom, 5, 5), + new Rectangle(0, img.Height - 5, 5, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.Right, rect.Bottom, 5, 5), + new Rectangle(img.Width - 5, img.Height - 5, 5, 5), GraphicsUnit.Pixel); + //four sides + g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y, 5, rect.Height), + new Rectangle(0, 5, 5, img.Height - 10), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.X, rect.Y - 5, rect.Width, 5), + new Rectangle(5, 0, img.Width - 10, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.Right, rect.Y, 5, rect.Height), + new Rectangle(img.Width - 5, 5, 5, img.Height - 10), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.X, rect.Bottom, rect.Width, 5), + new Rectangle(5, img.Height - 5, img.Width - 10, 5), GraphicsUnit.Pixel); + } + + #endregion private + + #region public -------------------------------------------------------------------------------------------------------- + /// + /// Find by canvas coordinates + /// + /// Coordinates in canvas + /// Data found + public NodeFindInfo FindNodeFromPoint(PointF pt) { + m_find.Node = null; m_find.NodeOption = null; m_find.Mark = null; + for (int i = this._Nodes.Count - 1; i >= 0; i--) { + if (!string.IsNullOrEmpty(this._Nodes[i].Mark) && this.PointInRectangle(this._Nodes[i].MarkRectangle, pt.X, pt.Y)) { + m_find.Mark = this._Nodes[i].Mark; + m_find.MarkLines = this._Nodes[i].MarkLines; + return m_find; + } + foreach (STNodeOption v in this._Nodes[i].InputOptions) { + if (v == STNodeOption.Empty) continue; + if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v; + } + foreach (STNodeOption v in this._Nodes[i].OutputOptions) { + if (v == STNodeOption.Empty) continue; + if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v; + } + if (this.PointInRectangle(this._Nodes[i].Rectangle, pt.X, pt.Y)) { + m_find.Node = this._Nodes[i]; + } + if (m_find.NodeOption != null || m_find.Node != null) return m_find; + } + return m_find; + } + /// + /// Get the Node collection that has been selected + /// + /// Node array + public STNode[] GetSelectedNode() { + return m_hs_node_selected.ToArray(); + } + /// + /// Convert canvas coordinates to control coordinates + /// + /// parameter + /// Whether it is the X coordinate + /// Converted coordinates + public float CanvasToControl(float number, bool isX) { + return (number * this._CanvasScale) + (isX ? this._CanvasOffsetX : this._CanvasOffsetY); + } + /// + /// Convert canvas coordinates to control coordinates + /// + /// coordinates + /// Converted coordinates + public PointF CanvasToControl(PointF pt) { + pt.X = (pt.X * this._CanvasScale) + this._CanvasOffsetX; + pt.Y = (pt.Y * this._CanvasScale) + this._CanvasOffsetY; + //pt.X += this._CanvasOffsetX; + //pt.Y += this._CanvasOffsetY; + return pt; + } + /// + /// Convert canvas coordinates to control coordinates + /// + /// coordinates + /// Converted coordinates + public Point CanvasToControl(Point pt) { + pt.X = (int)(pt.X * this._CanvasScale + this._CanvasOffsetX); + pt.Y = (int)(pt.Y * this._CanvasScale + this._CanvasOffsetY); + //pt.X += (int)this._CanvasOffsetX; + //pt.Y += (int)this._CanvasOffsetY; + return pt; + } + /// + /// Convert canvas coordinates to control coordinates + /// + /// rectangular area + /// Transformed rectangular area + public Rectangle CanvasToControl(Rectangle rect) { + rect.X = (int)((rect.X * this._CanvasScale) + this._CanvasOffsetX); + rect.Y = (int)((rect.Y * this._CanvasScale) + this._CanvasOffsetY); + rect.Width = (int)(rect.Width * this._CanvasScale); + rect.Height = (int)(rect.Height * this._CanvasScale); + //rect.X += (int)this._CanvasOffsetX; + //rect.Y += (int)this._CanvasOffsetY; + return rect; + } + /// + /// Convert canvas coordinates to control coordinates + /// + /// rectangular area + /// Transformed rectangular area + public RectangleF CanvasToControl(RectangleF rect) { + rect.X = (rect.X * this._CanvasScale) + this._CanvasOffsetX; + rect.Y = (rect.Y * this._CanvasScale) + this._CanvasOffsetY; + rect.Width = (rect.Width * this._CanvasScale); + rect.Height = (rect.Height * this._CanvasScale); + //rect.X += this._CanvasOffsetX; + //rect.Y += this._CanvasOffsetY; + return rect; + } + /// + /// Convert control coordinates to canvas coordinates + /// + /// parameter + /// Whether it is the X coordinate + /// Converted coordinates + public float ControlToCanvas(float number, bool isX) { + return (number - (isX ? this._CanvasOffsetX : this._CanvasOffsetY)) / this._CanvasScale; + } + /// + /// Convert control coordinates to canvas coordinates + /// + /// coordinates + /// Converted coordinates + public Point ControlToCanvas(Point pt) { + pt.X = (int)((pt.X - this._CanvasOffsetX) / this._CanvasScale); + pt.Y = (int)((pt.Y - this._CanvasOffsetY) / this._CanvasScale); + return pt; + } + /// + /// Convert control coordinates to canvas coordinates + /// + /// coordinates + /// Converted coordinates + public PointF ControlToCanvas(PointF pt) { + pt.X = ((pt.X - this._CanvasOffsetX) / this._CanvasScale); + pt.Y = ((pt.Y - this._CanvasOffsetY) / this._CanvasScale); + return pt; + } + /// + /// Convert control coordinates to canvas coordinates + /// + /// rectangular area + /// Transformed area + public Rectangle ControlToCanvas(Rectangle rect) { + rect.X = (int)((rect.X - this._CanvasOffsetX) / this._CanvasScale); + rect.Y = (int)((rect.Y - this._CanvasOffsetY) / this._CanvasScale); + rect.Width = (int)(rect.Width / this._CanvasScale); + rect.Height = (int)(rect.Height / this._CanvasScale); + return rect; + } + /// + /// Convert control coordinates to canvas coordinates + /// + /// rectangular area + /// Transformed area + public RectangleF ControlToCanvas(RectangleF rect) { + rect.X = ((rect.X - this._CanvasOffsetX) / this._CanvasScale); + rect.Y = ((rect.Y - this._CanvasOffsetY) / this._CanvasScale); + rect.Width = (rect.Width / this._CanvasScale); + rect.Height = (rect.Height / this._CanvasScale); + return rect; + } + /// + /// Move the origin coordinates of the canvas to the specified control coordinates + /// Cannot move when Node does not exist + /// + /// X coordinate + /// Y coordinate + /// Whether to start the animation effect during the movement process + /// Specify the coordinate parameters to be modified + public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma) { + if (this._Nodes.Count == 0) { + m_real_canvas_x = m_real_canvas_y = 10; + return; + } + int l = (int)((this._CanvasValidBounds.Left + 50) * this._CanvasScale); + int t = (int)((this._CanvasValidBounds.Top + 50) * this._CanvasScale); + int r = (int)((this._CanvasValidBounds.Right - 50) * this._CanvasScale); + int b = (int)((this._CanvasValidBounds.Bottom - 50) * this._CanvasScale); + if (r + x < 0) x = -r; + if (this.Width - l < x) x = this.Width - l; + if (b + y < 0) y = -b; + if (this.Height - t < y) y = this.Height - t; + if (bAnimation) { + if ((ma & CanvasMoveArgs.Left) == CanvasMoveArgs.Left) + m_real_canvas_x = x; + if ((ma & CanvasMoveArgs.Top) == CanvasMoveArgs.Top) + m_real_canvas_y = y; + } else { + m_real_canvas_x = this._CanvasOffsetX = x; + m_real_canvas_y = this._CanvasOffsetY = y; + } + this.OnCanvasMoved(EventArgs.Empty); + } + /// + /// Scale the canvas + /// Cannot zoom when no Node exists + /// + /// Scale ratio + /// Zoom center X coordinates on the control + /// The coordinate of the zoom center Y on the control + public void ScaleCanvas(float f, float x, float y) { + if (this._Nodes.Count == 0) { + this._CanvasScale = 1F; + return; + } + if (this._CanvasScale == f) return; + if (f < 0.5) f = 0.5f; else if (f > 3) f = 3; + float x_c = this.ControlToCanvas(x, true); + float y_c = this.ControlToCanvas(y, false); + this._CanvasScale = f; + this._CanvasOffsetX = m_real_canvas_x -= this.CanvasToControl(x_c, true) - x; + this._CanvasOffsetY = m_real_canvas_y -= this.CanvasToControl(y_c, false) - y; + this.OnCanvasScaled(EventArgs.Empty); + this.Invalidate(); + } + /// + /// Get the corresponding relationship of the currently connected Option + /// + /// Connection information collection + public ConnectionInfo[] GetConnectionInfo() { + return m_dic_gp_info.Values.ToArray(); + } + /// + /// Determine if there is a connection path between two Nodes + /// + /// Start Node + /// Target Node + /// true if the path exists, otherwise false + public static bool CanFindNodePath(STNode nodeStart, STNode nodeFind) { + HashSet hs = new HashSet(); + return STNodeEditor.CanFindNodePath(nodeStart, nodeFind, hs); + } + private static bool CanFindNodePath(STNode nodeStart, STNode nodeFind, HashSet hs) { + foreach (STNodeOption op_1 in nodeStart.OutputOptions) { + foreach(STNodeOption op_2 in op_1.ConnectedOption) { + if (op_2.Owner == nodeFind) return true; + if (hs.Add(op_2.Owner)) { + if (STNodeEditor.CanFindNodePath(op_2.Owner, nodeFind)) return true; + } + } + } + return false; + } + /// + /// Get the image of the specified rectangular area in the canvas + /// + /// The rectangular area specified in the canvas + /// image + public Image GetCanvasImage(Rectangle rect) { return this.GetCanvasImage(rect, 1f); } + /// + /// Get the image of the specified rectangular area in the canvas + /// + /// The rectangular area specified in the canvas + /// Scale + /// image + public Image GetCanvasImage(Rectangle rect, float fScale) { + if (fScale < 0.5) fScale = 0.5f; else if (fScale > 3) fScale = 3; + Image img = new Bitmap((int)(rect.Width * fScale), (int)(rect.Height * fScale)); + using (Graphics g = Graphics.FromImage(img)) { + g.Clear(this.BackColor); + g.ScaleTransform (fScale, fScale); + m_drawing_tools.Graphics = g; + + if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, rect.Width, rect.Height); + g.TranslateTransform(-rect.X, -rect.Y); //Move the coordinate system + this.OnDrawNode(m_drawing_tools, rect); + this.OnDrawConnectedLine(m_drawing_tools); + + g.ResetTransform(); + + if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, img.Size, m_lst_node_out); + } + return img; + } + /// + /// Save the class content in the canvas to the file + /// + /// File path + public void SaveCanvas(string strFileName) { + using (FileStream fs = new FileStream(strFileName, FileMode.Create, FileAccess.Write)) { + this.SaveCanvas(fs); + } + } + /// + /// Save the class content in the canvas to the data stream + /// + /// Data stream object + public void SaveCanvas(Stream s) { + Dictionary dic = new Dictionary(); + s.Write(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0, 4); //file head + s.WriteByte(1); //ver + using (GZipStream gs = new GZipStream(s, CompressionMode.Compress)) { + gs.Write(BitConverter.GetBytes(this._CanvasOffsetX), 0, 4); + gs.Write(BitConverter.GetBytes(this._CanvasOffsetY), 0, 4); + gs.Write(BitConverter.GetBytes(this._CanvasScale), 0, 4); + gs.Write(BitConverter.GetBytes(this._Nodes.Count), 0, 4); + foreach (STNode node in this._Nodes) { + try { + byte[] byNode = node.GetSaveData(); + gs.Write(BitConverter.GetBytes(byNode.Length), 0, 4); + gs.Write(byNode, 0, byNode.Length); + foreach (STNodeOption op in node.InputOptions) if (!dic.ContainsKey(op)) dic.Add(op, dic.Count); + foreach (STNodeOption op in node.OutputOptions) if (!dic.ContainsKey(op)) dic.Add(op, dic.Count); + } catch (Exception ex) { + throw new Exception("Error getting node data-" + node.Title, ex); + } + } + gs.Write(BitConverter.GetBytes(m_dic_gp_info.Count), 0, 4); + foreach (var v in m_dic_gp_info.Values) + gs.Write(BitConverter.GetBytes(((dic[v.Output] << 32) | dic[v.Input])), 0, 8); + } + } + /// + /// Get the binary data of the content in the canvas + /// + /// Binary data + public byte[] GetCanvasData() { + using (MemoryStream ms = new MemoryStream()) { + this.SaveCanvas(ms); + return ms.ToArray(); + } + } + /// + /// Load the assembly + /// + /// Assembly Collection + /// Number of files of STNode type + public int LoadAssembly(string[] strFiles) { + int nCount = 0; + foreach (var v in strFiles) { + try { + if (this.LoadAssembly(v)) nCount++; + } catch { } + } + return nCount; + } + /// + /// Load the assembly + /// + /// Specify the file to be loaded + /// Whether the load is successful + public bool LoadAssembly(string strFile) { + bool bFound = false; + Assembly asm = Assembly.LoadFrom(strFile); + if (asm == null) return false; + foreach (var t in asm.GetTypes()) { + if (t.IsAbstract) continue; + if (t == m_type_node || t.IsSubclassOf(m_type_node)) { + if (m_dic_type.ContainsKey(t.GUID.ToString())) continue; + m_dic_type.Add(t.GUID.ToString(), t); + bFound = true; + } + } + return bFound; + } + /// + /// Get the Node type loaded in the current editor + /// + /// Type Collection + public Type[] GetTypes() { + return m_dic_type.Values.ToArray(); + } + /// + /// Load data from file + /// Note: This method does not clear the data in the canvas but superimposes the data + /// + /// File path + public void LoadCanvas(string strFileName) { + using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(strFileName))) + this.LoadCanvas(ms); + } + /// + /// Load data from binary + /// Note: This method does not clear the data in the canvas but superimposes the data + /// + /// Binary data + public void LoadCanvas(byte[] byData) { + using (MemoryStream ms = new MemoryStream(byData)) + this.LoadCanvas(ms); + } + /// + /// Load data from the data stream + /// Note: This method does not clear the data in the canvas but superimposes the data + /// + /// Data stream object + public void LoadCanvas(Stream s) { + int nLen = 0; + byte[] byLen = new byte[4]; + s.Read(byLen, 0, 4); + if (BitConverter.ToInt32(byLen, 0) != BitConverter.ToInt32(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0)) + throw new InvalidDataException("Unrecognized file type"); + if (s.ReadByte() != 1) throw new InvalidDataException("Unrecognized file version number"); + using (GZipStream gs = new GZipStream(s, CompressionMode.Decompress)) { + gs.Read(byLen, 0, 4); + float x = BitConverter.ToSingle(byLen, 0); + gs.Read(byLen, 0, 4); + float y = BitConverter.ToSingle(byLen, 0); + gs.Read(byLen, 0, 4); + float scale = BitConverter.ToSingle(byLen, 0); + gs.Read(byLen, 0, 4); + int nCount = BitConverter.ToInt32(byLen, 0); + Dictionary dic = new Dictionary(); + HashSet hs = new HashSet(); + byte[] byData = null; + for (int i = 0; i < nCount; i++) { + gs.Read(byLen, 0, byLen.Length); + nLen = BitConverter.ToInt32(byLen, 0); + byData = new byte[nLen]; + gs.Read(byData, 0, byData.Length); + STNode node = null; + try { node = this.GetNodeFromData(byData); } catch (Exception ex) { + throw new Exception("An error occurred while loading the node, the data may be corrupted\r\n" + ex.Message, ex); + } + try { this._Nodes.Add(node); } catch (Exception ex) { + throw new Exception("Error loading node-" + node.Title, ex); + } + foreach (STNodeOption op in node.InputOptions) if (hs.Add(op)) dic.Add(dic.Count, op); + foreach (STNodeOption op in node.OutputOptions) if (hs.Add(op)) dic.Add(dic.Count, op); + } + gs.Read(byLen, 0, 4); + nCount = BitConverter.ToInt32(byLen, 0); + byData = new byte[8]; + for (int i = 0; i < nCount; i++) { + gs.Read(byData, 0, byData.Length); + long id = BitConverter.ToInt64(byData, 0); + long op_out = id >> 32; + long op_in = (int)id; + dic[op_out].ConnectOption(dic[op_in]); + } + this.ScaleCanvas(scale, 0, 0); + this.MoveCanvas(x, y, false, CanvasMoveArgs.All); + } + this.BuildBounds(); + foreach (STNode node in this._Nodes) node.OnEditorLoadCompleted(); + } + + private STNode GetNodeFromData(byte[] byData) { + int nIndex = 0; + string strModel = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]); + nIndex += byData[nIndex] + 1; + string strGUID = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]); + nIndex += byData[nIndex] + 1; + + int nLen = 0; + + Dictionary dic = new Dictionary(); + while (nIndex < byData.Length) { + nLen = BitConverter.ToInt32(byData, nIndex); + nIndex += 4; + string strKey = Encoding.UTF8.GetString(byData, nIndex, nLen); + nIndex += nLen; + nLen = BitConverter.ToInt32(byData, nIndex); + nIndex += 4; + byte[] byValue = new byte[nLen]; + Array.Copy(byData, nIndex, byValue, 0, nLen); + nIndex += nLen; + dic.Add(strKey, byValue); + } + if (!m_dic_type.ContainsKey(strGUID)) throw new TypeLoadException("Cannot find the assembly where the type {" + strModel.Split('|')[1] + "} is located to ensure that the assembly {" + strModel.Split('|')[0] + "} has been loaded correctly by the editor. The assembly can be loaded by calling LoadAssembly()"); + Type t = m_dic_type[strGUID]; ; + STNode node = (STNode)Activator.CreateInstance(t); + node.OnLoadNode(dic); + return node; + } + /// + /// Display the prompt information in the canvas + /// + /// Information to display + /// Information foreground color + /// Information background color + public void ShowAlert(string strText, Color foreColor, Color backColor) { + this.ShowAlert(strText, foreColor, backColor, 1000, AlertLocation.RightBottom, true); + } + /// + /// Display the prompt information in the canvas + /// + /// Information to display + /// Information foreground color + /// Information background color + /// The location where the information is to be displayed + public void ShowAlert(string strText, Color foreColor, Color backColor, AlertLocation al) { + this.ShowAlert(strText, foreColor, backColor, 1000, al, true); + } + /// + /// Display the prompt information in the canvas + /// + /// Information to display + /// Information foreground color + /// Information background color + /// message duration + /// The location where the information is to be displayed + /// Whether to redraw immediately + public void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw) { + m_str_alert = strText; + m_forecolor_alert = foreColor; + m_backcolor_alert = backColor; + m_time_alert = nTime; + m_dt_alert = DateTime.Now; + m_alpha_alert = 255; + m_al = al; + if (bRedraw) this.Invalidate(); + } + /// + /// Set the active node in the canvas + /// + /// The node that needs to be set as active + /// The active node before setting + public STNode SetActiveNode(STNode node) { + if (node != null && !this._Nodes.Contains(node)) return this._ActiveNode; + STNode ret = this._ActiveNode; + if (this._ActiveNode != node) { //Reset active selection node + if (node != null) { + this._Nodes.MoveToEnd(node); + node.IsActive = true; + node.SetSelected(true, false); + node.OnGotFocus(EventArgs.Empty); + } + if (this._ActiveNode != null) { + this._ActiveNode.IsActive /*= this._ActiveNode.IsSelected*/ = false; + this._ActiveNode.OnLostFocus(EventArgs.Empty); + } + this._ActiveNode = node; + this.Invalidate(); + this.OnActiveChanged(EventArgs.Empty); + //this.OnSelectedChanged(EventArgs.Empty); + } + return ret; + } + /// + /// Add a selected node to the canvas + /// + /// The node that needs to be selected + /// Whether the addition is successful + public bool AddSelectedNode(STNode node) { + if (!this._Nodes.Contains(node)) return false; + bool b = !node.IsSelected; + node.IsSelected = true; + lock (m_hs_node_selected) return m_hs_node_selected.Add(node) || b; + } + /// + /// Remove a selected node from the canvas + /// + /// Node to be removed + /// Is the removal successful or not + public bool RemoveSelectedNode(STNode node) { + if (!this._Nodes.Contains(node)) return false; + bool b = node.IsSelected; + node.IsSelected = false; + lock (m_hs_node_selected) return m_hs_node_selected.Remove(node) || b; + } + /// + /// Adds a default data type color to the editor + /// + /// Data Type + /// Corresponding color + /// The set color + public Color SetTypeColor(Type t, Color clr) { + return this.SetTypeColor(t, clr, false); + } + /// + /// Adds a default data type color to the editor + /// + /// Data Type + /// Corresponding color + /// Replace the color if it already exists + /// The set color + public Color SetTypeColor(Type t, Color clr, bool bReplace) { + if (this._TypeColor.ContainsKey(t)) { + if (bReplace) this._TypeColor[t] = clr; + } else { + this._TypeColor.Add(t, clr); + } + return this._TypeColor[t]; + } + + #endregion public + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeEditorDataType.cs b/CodeWalker.WinForms/STNodeEditor/STNodeEditorDataType.cs new file mode 100644 index 0000000..218d713 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeEditorDataType.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Drawing; + +namespace ST.Library.UI.NodeEditor +{ + public enum ConnectionStatus + { + /// + /// No owner exists + /// + [Description("No owner exists")] + NoOwner, + /// + /// same owner + /// + [Description("same owner")] + SameOwner, + /// + /// Both are input or output options + /// + [Description("both input or output options")] + SameInputOrOutput, + /// + /// Different data types + /// + [Description("Different data types")] + ErrorType, + /// + /// Single connection node + /// + [Description("Single connection node")] + SingleOption, + /// + /// A circular path appears + /// + [Description("A circular path appears")] + Loop, + /// + /// Existing connection + /// + [Description("existing connection")] + Exists, + /// + /// blank options + /// + [Description("Blank option")] + EmptyOption, + /// + /// already connected + /// + [Description("Connected")] + Connected, + /// + /// The connection is disconnected + /// + [Description("The connection was disconnected")] + DisConnected, + /// + /// Node is locked + /// + [Description("Node is locked")] + Locked, + /// + /// Operation rejected + /// + [Description("Operation denied")] + Reject, + /// + /// is being connected + /// + [Description("being connected")] + Connecting, + /// + /// Disconnecting + /// + [Description("Disconnecting")] + DisConnecting + } + + public enum AlertLocation + { + Left, + Top, + Right, + Bottom, + Center, + LeftTop, + RightTop, + RightBottom, + LeftBottom, + } + + public struct DrawingTools + { + public Graphics Graphics; + public Pen Pen; + public SolidBrush SolidBrush; + } + + public enum CanvasMoveArgs //View the parameters needed when moving the canvas ->MoveCanvas() + { + Left = 1, //indicates that only the X coordinate is moved + Top = 2, //Indicates that only the Y coordinate is moved + All = 4 //Indicates that XY move at the same time + } + + public struct NodeFindInfo + { + public STNode Node; + public STNodeOption NodeOption; + public string Mark; + public string[] MarkLines; + } + + public struct ConnectionInfo + { + public STNodeOption Input; + public STNodeOption Output; + } + + public delegate void STNodeOptionEventHandler(object sender, STNodeOptionEventArgs e); + + public class STNodeOptionEventArgs : EventArgs + { + private STNodeOption _TargetOption; + /// + /// The corresponding Option that triggers this event + /// + public STNodeOption TargetOption { + get { return _TargetOption; } + } + + private ConnectionStatus _Status; + /// + /// Connection status between Option + /// + public ConnectionStatus Status { + get { return _Status; } + internal set { _Status = value; } + } + + private bool _IsSponsor; + /// + /// Whether it is the initiator of this behavior + /// + public bool IsSponsor { + get { return _IsSponsor; } + } + + public STNodeOptionEventArgs(bool isSponsor, STNodeOption opTarget, ConnectionStatus cr) { + this._IsSponsor = isSponsor; + this._TargetOption = opTarget; + this._Status = cr; + } + } + + public delegate void STNodeEditorEventHandler(object sender, STNodeEditorEventArgs e); + public delegate void STNodeEditorOptionEventHandler(object sender, STNodeEditorOptionEventArgs e); + + + public class STNodeEditorEventArgs : EventArgs + { + private STNode _Node; + + public STNode Node { + get { return _Node; } + } + + public STNodeEditorEventArgs(STNode node) { + this._Node = node; + } + } + + public class STNodeEditorOptionEventArgs : STNodeOptionEventArgs + { + + private STNodeOption _CurrentOption; + /// + /// Option to actively trigger events + /// + public STNodeOption CurrentOption { + get { return _CurrentOption; } + } + + private bool _Continue = true; + /// + /// Whether to continue the downward operation for Begin(Connecting/DisConnecting) whether to continue the backward operation + /// + public bool Continue { + get { return _Continue; } + set { _Continue = value; } + } + + public STNodeEditorOptionEventArgs(STNodeOption opTarget, STNodeOption opCurrent, ConnectionStatus cr) + : base(false, opTarget, cr) { + this._CurrentOption = opCurrent; + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeEditorPanel.cs b/CodeWalker.WinForms/STNodeEditor/STNodeEditorPanel.cs new file mode 100644 index 0000000..80cf27f --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeEditorPanel.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Windows.Forms; +using System.Drawing; +using System.Runtime.InteropServices; +using System.ComponentModel; + +namespace ST.Library.UI.NodeEditor +{ + public class STNodeEditorPannel : Control + { + private bool _LeftLayout = true; + /// + /// Gets or sets whether it is the left layout + /// + [Description("Get or set whether it is the left layout"), DefaultValue(true)] + public bool LeftLayout { + get { return _LeftLayout; } + set { + if (value == _LeftLayout) return; + _LeftLayout = value; + this.SetLocation(); + this.Invalidate(); + } + } + + private Color _SplitLineColor = Color.Black; + /// + /// Gets or this is the color of the dividing line + /// + [Description("Get or set the color of the dividing line"), DefaultValue(typeof(Color), "Black")] + public Color SplitLineColor { + get { return _SplitLineColor; } + set { _SplitLineColor = value; } + } + + private Color _HandleLineColor = Color.Gray; + /// + /// Gets or sets the color of the dividing line handle + /// + [Description("Get or set the color of the dividing line handle"), DefaultValue(typeof(Color), "Gray")] + public Color HandleLineColor { + get { return _HandleLineColor; } + set { _HandleLineColor = value; } + } + + private bool _ShowScale = true; + /// + /// Get or set the display scale when the editor is zoomed + /// + [Description("Get or set the display scale when the editor is zoomed"), DefaultValue(true)] + public bool ShowScale { + get { return _ShowScale; } + set { _ShowScale = value; } + } + + private bool _ShowConnectionStatus = true; + /// + /// Get or set whether to display the status when the node is connected + /// + [Description("Get or set whether to display the status when the node is connected"), DefaultValue(true)] + public bool ShowConnectionStatus { + get { return _ShowConnectionStatus; } + set { _ShowConnectionStatus = value; } + } + + private int _X; + /// + /// Gets or sets the horizontal width of the dividing line + /// + [Description("Get or set the horizontal width of the dividing line"), DefaultValue(201)] + public int X { + get { return _X; } + set { + if (value < 122) value = 122; + else if (value > this.Width - 122) value = this.Width - 122; + if (_X == value) return; + _X = value; + this.SetLocation(); + } + } + + private int _Y; + /// + /// Gets or sets the vertical height of the dividing line + /// + [Description("Get or set the vertical height of the dividing line")] + public int Y { + get { return _Y; } + set { + if (value < 122) value = 122; + else if (value > this.Height - 122) value = this.Height - 122; + if (_Y == value) return; + _Y = value; + this.SetLocation(); + } + } + /// + /// Get the STNodeEditor in the panel + /// + [Description("Get the STNodeEditor in the panel"), Browsable(false)] + public STNodeEditor Editor { + get { return m_editor; } + } + /// + /// Get the STNodeTreeView in the panel + /// + [Description("Get the STNodeTreeView in the panel"), Browsable(false)] + public STNodeTreeView TreeView { + get { return m_tree; } + } + /// + /// Get the STNodePropertyGrid in the panel + /// + [Description("Get the STNodePropertyGrid in the panel"), Browsable(false)] + public STNodePropertyGrid PropertyGrid { + get { return m_grid; } + } + + private Point m_pt_down; + private bool m_is_mx; + private bool m_is_my; + private Pen m_pen; + + private bool m_nInited; + private Dictionary m_dic_status_key = new Dictionary(); + + private STNodeEditor m_editor; + private STNodeTreeView m_tree; + private STNodePropertyGrid m_grid; + + public override Size MinimumSize { + get { + return base.MinimumSize; + } + set { + value = new Size(250, 250); + base.MinimumSize = value; + } + } + + [DllImport("user32.dll")] + private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool bRedraw); + + public STNodeEditorPannel() { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + m_editor = new STNodeEditor(); + m_tree = new STNodeTreeView(); + m_grid = new STNodePropertyGrid(); + m_grid.Text = "NodeProperty"; + this.Controls.Add(m_editor); + this.Controls.Add(m_tree); + this.Controls.Add(m_grid); + this.Size = new Size(500, 500); + this.MinimumSize = new Size(250, 250); + this.BackColor = Color.FromArgb(255, 34, 34, 34); + + m_pen = new Pen(this.BackColor, 3); + + Type t = typeof(ConnectionStatus); + var vv = Enum.GetValues(t); + var vvv = vv.GetValue(0); + foreach (var f in t.GetFields()) { + if (!f.FieldType.IsEnum) continue; + foreach (var a in f.GetCustomAttributes(true)) { + if (!(a is DescriptionAttribute)) continue; + m_dic_status_key.Add((ConnectionStatus)f.GetValue(f), ((DescriptionAttribute)a).Description); + } + } + + m_editor.ActiveChanged += (s, e) => m_grid.SetNode(m_editor.ActiveNode); + m_editor.CanvasScaled += (s, e) => { + if (this._ShowScale) + m_editor.ShowAlert(m_editor.CanvasScale.ToString("F2"), Color.White, Color.FromArgb(127, 255, 255, 0)); + }; + m_editor.OptionConnected += (s, e) => { + if (this._ShowConnectionStatus) + m_editor.ShowAlert(m_dic_status_key[e.Status], Color.White, e.Status == ConnectionStatus.Connected ? Color.FromArgb(125, Color.Lime) : Color.FromArgb(125, Color.Red)); + }; + } + + protected override void OnResize(EventArgs e) { + base.OnResize(e); + if (!m_nInited) { + this._Y = this.Height / 2; + if (this._LeftLayout) + this._X = 201; + else + this._X = this.Width - 202; + m_nInited = true; + this.SetLocation(); + return; + } + this.SetLocation(); + } + + protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) { + if (width < 250) width = 250; + if (height < 250) height = 250; + base.SetBoundsCore(x, y, width, height, specified); + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + m_pen.Width = 3; + m_pen.Color = this._SplitLineColor; + g.DrawLine(m_pen, this._X, 0, this._X, this.Height); + int nX = 0; + if (this._LeftLayout) { + g.DrawLine(m_pen, 0, this._Y, this._X - 1, this._Y); + nX = this._X / 2; + } else { + g.DrawLine(m_pen, this._X + 2, this._Y, this.Width, this._Y); + nX = this._X + (this.Width - this._X) / 2; + } + m_pen.Width = 1; + this._HandleLineColor = Color.Gray; + m_pen.Color = this._HandleLineColor; + g.DrawLine(m_pen, this._X, this._Y - 10, this._X, this._Y + 10); + g.DrawLine(m_pen, nX - 10, this._Y, nX + 10, this._Y); + } + + private void SetLocation() { + if (this._LeftLayout) { + //m_tree.Location = Point.Empty; + //m_tree.Size = new Size(m_sx - 1, m_sy - 1); + STNodeEditorPannel.MoveWindow(m_tree.Handle, 0, 0, this._X - 1, this._Y - 1, false); + + //m_grid.Location = new Point (0, m_sy + 2); + //m_grid.Size = new Size(m_sx - 1, this.Height - m_sy - 2); + STNodeEditorPannel.MoveWindow(m_grid.Handle, 0, this._Y + 2, this._X - 1, this.Height - this._Y - 2, false); + + //m_editor.Location = new Point(m_sx + 2, 0); + //m_editor.Size = new Size(this.Width - m_sx - 2, this.Height); + STNodeEditorPannel.MoveWindow(m_editor.Handle, this._X + 2, 0, this.Width - this._X - 2, this.Height, false); + } else { + STNodeEditorPannel.MoveWindow(m_editor.Handle, 0, 0, this._X - 1, this.Height, false); + STNodeEditorPannel.MoveWindow(m_tree.Handle, this._X + 2, 0, this.Width - this._X - 2, this._Y - 1, false); + STNodeEditorPannel.MoveWindow(m_grid.Handle, this._X + 2, this._Y + 2, this.Width - this._X - 2, this.Height - this._Y - 2, false); + } + } + + protected override void OnMouseDown(MouseEventArgs e) { + base.OnMouseDown(e); + m_pt_down = e.Location; + m_is_mx = m_is_my = false; + if (this.Cursor == Cursors.VSplit) { + m_is_mx = true; + } else if (this.Cursor == Cursors.HSplit) { + m_is_my = true; + } + } + + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + if (e.Button == MouseButtons.Left) { + int nw = 122;// (int)(this.Width * 0.1f); + int nh = 122;// (int)(this.Height * 0.1f); + if (m_is_mx) { + this._X = e.X;// -m_pt_down.X; + if (this._X < nw) this._X = nw; + else if (_X + nw > this.Width) this._X = this.Width - nw; + } else if (m_is_my) { + this._Y = e.Y; + if (this._Y < nh) this._Y = nh; + else if (this._Y + nh > this.Height) this._Y = this.Height - nh; + } + //m_rx = this.Width - m_sx;// (float)m_sx / this.Width; + //m_fh = (float)m_sy / this.Height; + this.SetLocation(); + this.Invalidate(); + return; + } + if (Math.Abs(e.X - this._X) < 2) + this.Cursor = Cursors.VSplit; + else if (Math.Abs(e.Y - this._Y) < 2) + this.Cursor = Cursors.HSplit; + else this.Cursor = Cursors.Arrow; + } + + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + m_is_mx = m_is_my = false; + this.Cursor = Cursors.Arrow; + } + /// + /// Add a STNode to the tree control + /// + /// STNode type + /// Whether the addition is successful + public bool AddSTNode(Type stNodeType) { + return m_tree.AddNode(stNodeType); + } + /// + /// Load STNode from assembly + /// + /// Assembly path + /// Add success number + public int LoadAssembly(string strFileName) { + m_editor.LoadAssembly(strFileName); + return m_tree.LoadAssembly(strFileName); + } + /// + /// Sets the text for the editor to display the connection status + /// + /// Connection status + /// Corresponding display text + /// Old text + public string SetConnectionStatusText(ConnectionStatus status, string strText) { + string strOld = null; + if (m_dic_status_key.ContainsKey(status)) { + strOld = m_dic_status_key[status]; + m_dic_status_key[status] = strText; + return strOld; + } + m_dic_status_key.Add(status, strText); + return strText; + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeHub.cs b/CodeWalker.WinForms/STNodeEditor/STNodeHub.cs new file mode 100644 index 0000000..0041e83 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeHub.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +/* +MIT License + +Copyright (c) 2021 DebugST@crystal_lz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/* + * create: 2021-12-08 + * modify: 2021-03-02 + * Author: Crystal_lz + * blog: http://st233.com + * Gitee: https://gitee.com/DebugST + * Github: https://github.com/DebugST + */ +namespace ST.Library.UI.NodeEditor +{ + public class STNodeHub : STNode + { + private bool m_bSingle; + private string m_strIn; + private string m_strOut; + + public STNodeHub() : this(false, "IN", "OUT") { } + public STNodeHub(bool bSingle) : this(bSingle, "IN", "OUT") { } + public STNodeHub(bool bSingle, string strTextIn, string strTextOut) { + m_bSingle = bSingle; + m_strIn = strTextIn; + m_strOut = strTextOut; + this.Addhub(); + this.Title = "HUB"; + this.AutoSize = false; + this.TitleColor = System.Drawing.Color.FromArgb(200, System.Drawing.Color.DarkOrange); + } + + protected override void OnOwnerChanged() { + base.OnOwnerChanged(); + if (this.Owner == null) return; + using (Graphics g = this.Owner.CreateGraphics()) { + this.Width = base.GetDefaultNodeSize(g).Width; + } + } + + private void Addhub() { + var input = new STNodeHubOption(m_strIn, typeof(object), m_bSingle); + var output = new STNodeHubOption(m_strOut, typeof(object), false); + this.InputOptions.Add(input); + this.OutputOptions.Add(output); + input.Connected += new STNodeOptionEventHandler(input_Connected); + input.DataTransfer += new STNodeOptionEventHandler(input_DataTransfer); + input.DisConnected += new STNodeOptionEventHandler(input_DisConnected); + output.Connected += new STNodeOptionEventHandler(output_Connected); + output.DisConnected += new STNodeOptionEventHandler(output_DisConnected); + this.Height = this.TitleHeight + this.InputOptions.Count * 20; + } + + void output_DisConnected(object sender, STNodeOptionEventArgs e) { + STNodeOption op = sender as STNodeOption; + if (op.ConnectionCount != 0) return; + int nIndex = this.OutputOptions.IndexOf(op); + if (this.InputOptions[nIndex].ConnectionCount != 0) return; + this.InputOptions.RemoveAt(nIndex); + this.OutputOptions.RemoveAt(nIndex); + if (this.Owner != null) this.Owner.BuildLinePath(); + this.Height -= 20; + } + + void output_Connected(object sender, STNodeOptionEventArgs e) { + STNodeOption op = sender as STNodeOption; + int nIndex = this.OutputOptions.IndexOf(op); + var t = typeof(object); + if (this.InputOptions[nIndex].DataType == t) { + op.DataType = e.TargetOption.DataType; + this.InputOptions[nIndex].DataType = op.DataType; + foreach (STNodeOption v in this.InputOptions) { + if (v.DataType == t) return; + } + this.Addhub(); + } + } + + void input_DisConnected(object sender, STNodeOptionEventArgs e) { + STNodeOption op = sender as STNodeOption; + if (op.ConnectionCount != 0) return; + int nIndex = this.InputOptions.IndexOf(op); + if (this.OutputOptions[nIndex].ConnectionCount != 0) return; + this.InputOptions.RemoveAt(nIndex); + this.OutputOptions.RemoveAt(nIndex); + if (this.Owner != null) this.Owner.BuildLinePath(); + this.Height -= 20; + } + + void input_DataTransfer(object sender, STNodeOptionEventArgs e) { + STNodeOption op = sender as STNodeOption; + int nIndex = this.InputOptions.IndexOf(op); + if (e.Status != ConnectionStatus.Connected) + this.OutputOptions[nIndex].Data = null; + else + this.OutputOptions[nIndex].Data = e.TargetOption.Data; + this.OutputOptions[nIndex].TransferData(); + } + + void input_Connected(object sender, STNodeOptionEventArgs e) { + STNodeOption op = sender as STNodeOption; + int nIndex = this.InputOptions.IndexOf(op); + var t = typeof(object); + if (op.DataType == t) { + op.DataType = e.TargetOption.DataType; + this.OutputOptions[nIndex].DataType = op.DataType; + foreach (STNodeOption v in this.InputOptions) { + if (v.DataType == t) return; + } + this.Addhub(); + } else { + //this.OutputOptions[nIndex].Data = e.TargetOption.Data; + this.OutputOptions[nIndex].TransferData(e.TargetOption.Data); + } + } + + protected override void OnSaveNode(Dictionary dic) { + dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount)); + //dic.Add("single", new byte[] { (byte)(m_bSingle ? 1 : 0) }); + //dic.Add("strin", Encoding.UTF8.GetBytes(m_strIn)); + //dic.Add("strout", Encoding.UTF8.GetBytes(m_strOut)); + } + + protected internal override void OnLoadNode(Dictionary dic) { + base.OnLoadNode(dic); + int nCount = BitConverter.ToInt32(dic["count"], 0); + while (this.InputOptionsCount < nCount && this.InputOptionsCount != nCount) this.Addhub(); + //m_bSingle = dic["single"][0] == 1; + //m_strIn = Encoding.UTF8.GetString(dic["strin"]); + //m_strOut = Encoding.UTF8.GetString(dic["strout"]); + } + + public class STNodeHubOption : STNodeOption + { + public STNodeHubOption(string strText, Type dataType, bool bSingle) : base(strText, dataType, bSingle) { } + + public override ConnectionStatus ConnectOption(STNodeOption op) { + var t = typeof(object); + if (this.DataType != t) return base.ConnectOption(op); + this.DataType = op.DataType; + var ret = base.ConnectOption(op); + if (ret != ConnectionStatus.Connected) this.DataType = t; + return ret; + } + + public override ConnectionStatus CanConnect(STNodeOption op) { + if (op == STNodeOption.Empty) return ConnectionStatus.EmptyOption; + if (this.DataType != typeof(object)) return base.CanConnect(op); + 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 (op.DataType == typeof(object)) return ConnectionStatus.ErrorType; + + if (!this.IsInput) return ConnectionStatus.Connected; + foreach (STNodeOption owner_input in this.Owner.InputOptions) { + foreach (STNodeOption o in owner_input.ConnectedOption) { + if (o == op) return ConnectionStatus.Exists; + } + } + return ConnectionStatus.Connected; ; + } + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeOption.cs b/CodeWalker.WinForms/STNodeEditor/STNodeOption.cs new file mode 100644 index 0000000..bc4d8c1 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeOption.cs @@ -0,0 +1,456 @@ +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 + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeOptionCollection.cs b/CodeWalker.WinForms/STNodeEditor/STNodeOptionCollection.cs new file mode 100644 index 0000000..194ddb5 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeOptionCollection.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Collections; + +namespace ST.Library.UI.NodeEditor +{ + public class STNodeOptionCollection : IList, ICollection, IEnumerable + { + /* + * Although the collection provides a complete data interface such as: Add,Remove,... + * But try not to use some removal operations such as: Remove,RemoveAt,Clear,this[index] = value,... + * Because the Owner of each Option is strictly bound in my definition, some operations such as removal or replacement will affect the change of the Owner + * So all the original connections will be disconnected and the DisConnect event will be triggered + * To ensure security, only inheritors can access the collection in STNode + */ + private int _Count; + public int Count { get { return _Count; } } + private STNodeOption[] m_options; + private STNode m_owner; + + private bool m_isInput; //Whether the current collection stores the input point + + internal STNodeOptionCollection(STNode owner, bool isInput) { + if (owner == null) throw new ArgumentNullException("Owner cannot be null"); + m_owner = owner; + m_isInput = isInput; + m_options = new STNodeOption[4]; + } + + public STNodeOption Add(string strText, Type dataType, bool bSingle) { + //not do this code -> out of bounds + //return m_options[this.Add(new STNodeOption(strText, dataType, bSingle))]; + int nIndex = this.Add(new STNodeOption(strText, dataType, bSingle)); + return m_options[nIndex]; + } + + public int Add(STNodeOption option) { + if (option == null) throw new ArgumentNullException("Add object cannot be null"); + this.EnsureSpace(1); + int nIndex = option == STNodeOption.Empty ? -1 : this.IndexOf(option); + if (-1 == nIndex) { + nIndex = this._Count; + option.Owner = m_owner; + option.IsInput = m_isInput; + m_options[this._Count++] = option; + this.Invalidate(); + } + return nIndex; + } + + public void AddRange(STNodeOption[] options) { + if (options == null) throw new ArgumentNullException("Add object cannot be null"); + this.EnsureSpace(options.Length); + foreach (var op in options) { + if (op == null) throw new ArgumentNullException("Add object cannot be null"); + if (-1 == this.IndexOf(op)) { + op.Owner = m_owner; + op.IsInput = m_isInput; + m_options[this._Count++] = op; + } + } + this.Invalidate(); + } + + public void Clear() { + for (int i = 0; i < this._Count; i++) m_options[i].Owner = null; + this._Count = 0; + m_options = new STNodeOption[4]; + this.Invalidate(); + } + + public bool Contains(STNodeOption option) { + return this.IndexOf(option) != -1; + } + + public int IndexOf(STNodeOption option) { + return Array.IndexOf(m_options, option); + } + + public void Insert(int index, STNodeOption option) { + if (index < 0 || index >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + if (option == null) + throw new ArgumentNullException("Insert object cannot be null"); + this.EnsureSpace(1); + for (int i = this._Count; i > index; i--) + m_options[i] = m_options[i - 1]; + option.Owner = m_owner; + m_options[index] = option; + this._Count++; + this.Invalidate(); + } + + public bool IsFixedSize { + get { return false; } + } + + public bool IsReadOnly { + get { return false; } + } + + public void Remove(STNodeOption option) { + int nIndex = this.IndexOf(option); + if (nIndex != -1) this.RemoveAt(nIndex); + } + + public void RemoveAt(int index) { + if (index < 0 || index >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + this._Count--; + m_options[index].Owner = null; + for (int i = index, Len = this._Count; i < Len; i++) + m_options[i] = m_options[i + 1]; + this.Invalidate(); + } + + public STNodeOption this[int index] { + get { + if (index < 0 || index >= this._Count) + throw new IndexOutOfRangeException("Index out of bounds"); + return m_options[index]; + } + set { throw new InvalidOperationException("No reassignment of elements"); } + } + + public void CopyTo(Array array, int index) { + if (array == null) + throw new ArgumentNullException("Array cannot be empty"); + m_options.CopyTo(array, index); + } + + public bool IsSynchronized { + get { return true; } + } + + public object SyncRoot { + get { return this; } + } + + public IEnumerator GetEnumerator() { + for (int i = 0, Len = this._Count; i < Len; i++) + yield return m_options[i]; + } + /// + /// Check if there is enough space to expand the capacity + /// + /// Number of elements to be added + private void EnsureSpace(int elements) { + if (elements + this._Count > m_options.Length) { + STNodeOption[] arrTemp = new STNodeOption[Math.Max(m_options.Length * 2, elements + this._Count)]; + m_options.CopyTo(arrTemp, 0); + m_options = arrTemp; + } + } + + protected void Invalidate() { + if (m_owner != null && m_owner.Owner != null) { + m_owner.BuildSize(true, true, true); + //m_owner.Invalidate();//.Owner.Invalidate(); + } + } + //=================================================================================== + int IList.Add(object value) { + return this.Add((STNodeOption)value); + } + + void IList.Clear() { + this.Clear(); + } + + bool IList.Contains(object value) { + return this.Contains((STNodeOption)value); + } + + int IList.IndexOf(object value) { + return this.IndexOf((STNodeOption)value); + } + + void IList.Insert(int index, object value) { + this.Insert(index, (STNodeOption)value); + } + + bool IList.IsFixedSize { + get { return this.IsFixedSize; } + } + + bool IList.IsReadOnly { + get { return this.IsReadOnly; } + } + + void IList.Remove(object value) { + this.Remove((STNodeOption)value); + } + + void IList.RemoveAt(int index) { + this.RemoveAt(index); + } + + object IList.this[int index] { + get { + return this[index]; + } + set { + this[index] = (STNodeOption)value; + } + } + + void ICollection.CopyTo(Array array, int index) { + this.CopyTo(array, index); + } + + int ICollection.Count { + get { return this._Count; } + } + + bool ICollection.IsSynchronized { + get { return this.IsSynchronized; } + } + + object ICollection.SyncRoot { + get { return this.SyncRoot; } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + + public STNodeOption[] ToArray() { + STNodeOption[] ops = new STNodeOption[this._Count]; + for (int i = 0; i < ops.Length; i++) + ops[i] = m_options[i]; + return ops; + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodePropertyAttribute.cs b/CodeWalker.WinForms/STNodeEditor/STNodePropertyAttribute.cs new file mode 100644 index 0000000..e8e6fc5 --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodePropertyAttribute.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; + +namespace ST.Library.UI.NodeEditor +{ + /// + /// STNode node attribute characteristics + /// Used to describe STNode node attribute information and behavior on the attribute editor + /// + public class STNodePropertyAttribute : Attribute + { + private string _Name; + /// + /// Get the name of the property that needs to be displayed on the property editor + /// + public string Name { + get { return _Name; } + } + + private string _Description; + /// + /// Get the description of the property that needs to be displayed on the property editor + /// + public string Description { + get { return _Description; } + } + + private Type _ConverterType = typeof(STNodePropertyDescriptor); + /// + /// Get the property descriptor type + /// + public Type DescriptorType { + get { return _ConverterType; } + set { _ConverterType = value; } + } + + /// + /// Constructs an STNode property attribute + /// + /// Name to be displayed + /// Description information to be displayed + public STNodePropertyAttribute(string strKey, string strDesc) { + this._Name = strKey; + this._Description = strDesc; + } + //private Type m_descriptor_type_base = typeof(STNodePropertyDescriptor); + } + /// + /// STNode property descriptor + /// Used to determine how to interact with the property's value on the property editor and to determine how the property's value will be drawn and interacted on the property editor + /// + public class STNodePropertyDescriptor + { + /// + /// Get the target node + /// + public STNode Node { get; internal set; } + /// + /// Get the node attribute editor control to which it belongs + /// + public STNodePropertyGrid Control { get; internal set; } + /// + /// Get the area where the option is located + /// + public Rectangle Rectangle { get; internal set; } + /// + /// Get the area where the option name is located + /// + public Rectangle RectangleL { get; internal set; } + /// + /// Get the area where the option value is located + /// + public Rectangle RectangleR { get; internal set; } + /// + /// Get the name of the option that needs to be displayed + /// + public string Name { get; internal set; } + /// + /// Get the description information corresponding to the attribute + /// + public string Description { get; internal set; } + /// + /// Get attribute information + /// + public PropertyInfo PropertyInfo { get; internal set; } + + private static Type m_t_int = typeof(int); + private static Type m_t_float = typeof(float); + private static Type m_t_double = typeof(double); + private static Type m_t_string = typeof(string); + private static Type m_t_bool = typeof(bool); + + private StringFormat m_sf; + + /// + /// Construct a descriptor + /// + public STNodePropertyDescriptor() { + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + } + + /// + /// Occurs when determining the position of the STNode property on the property editor + /// + protected internal virtual void OnSetItemLocation() { } + /// + /// Converts the property value in the form of a string to the value of the property target type + /// By default, only int float double string bool and Array of the above types are supported + /// If the target type is not in the above, please rewrite this function to convert it yourself + /// + /// Attribute value in string form + /// The value of the real target type of the attribute + protected internal virtual object GetValueFromString(string strText) { + Type t = this.PropertyInfo.PropertyType; + if (t == m_t_int) return int.Parse(strText); + if (t == m_t_float) return float.Parse(strText); + if (t == m_t_double) return double.Parse(strText); + if (t == m_t_string) return strText; + if (t == m_t_bool) return bool.Parse(strText); + if (t.IsEnum) { + return Enum.Parse(t, strText); + } else if (t.IsArray) { + var t_1 = t.GetElementType(); + if (t_1 == m_t_string) return strText.Split(','); + string[] strs = strText.Trim(new char[] { ' ', ',' }).Split(',');//add other place trim() + if (t_1 == m_t_int) { + int[] arr = new int[strs.Length]; + for (int i = 0; i < strs.Length; i++) arr[i] = int.Parse(strs[i].Trim()); + return arr; + } + if (t_1 == m_t_float) { + float[] arr = new float[strs.Length]; + for (int i = 0; i < strs.Length; i++) arr[i] = float.Parse(strs[i].Trim()); + return arr; + } + if (t_1 == m_t_int) { + double[] arr = new double[strs.Length]; + for (int i = 0; i < strs.Length; i++) arr[i] = double.Parse(strs[i].Trim()); + return arr; + } + if (t_1 == m_t_int) { + bool[] arr = new bool[strs.Length]; + for (int i = 0; i < strs.Length; i++) arr[i] = bool.Parse(strs[i].Trim()); + return arr; + } + } + throw new InvalidCastException("Unable to complete the conversion of [string] to [" + t.FullName + "], please overload [STNodePropertyDescriptor.GetValueFromString(string)]"); + } + /// + /// Converts the value of the attribute target type to a value in the form of a string + /// ToString() operation on type value by default + /// If you need special processing, please rewrite this function to convert it yourself + /// + /// String form of attribute value + protected internal virtual string GetStringFromValue() { + var v = this.PropertyInfo.GetValue(this.Node, null); + var t = this.PropertyInfo.PropertyType; + if (v == null) return null; + if (t.IsArray) { + List lst = new List(); + foreach (var item in (Array)v) lst.Add(item.ToString()); + return string.Join(",", lst.ToArray()); + } + return v.ToString(); + } + /// + /// Convert the attribute value in binary form to the value of the attribute target type for restoring the attribute value from the data in the file store + /// Convert it to a string by default and then call GetValueFromString(string) + /// This function corresponds to GetBytesFromValue(). If the function needs to be rewritten, the two functions should be rewritten together + /// + /// Binary data + /// The value of the real target type of the attribute + protected internal virtual object GetValueFromBytes(byte[] byData) { + if (byData == null) return null; + string strText = Encoding.UTF8.GetString(byData); + return this.GetValueFromString(strText); + } + /// + /// Called when converting the value of the attribute target type to a binary value for file storage + /// Calls GetStringFromValue() by default and converts the string to binary data + /// For special handling, please rewrite this function to convert it yourself and rewrite GetValueFromBytes() + /// + /// Binary form of attribute value + protected internal virtual byte[] GetBytesFromValue() { + string strText = this.GetStringFromValue(); + if (strText == null) return null; + return Encoding.UTF8.GetBytes(strText); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.GetValue() + /// + /// The optional index value of the indexed attribute should be null for non-indexed attributes + /// Attribute value + protected internal virtual object GetValue(object[] index) { + return this.PropertyInfo.GetValue(this.Node, index); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.SetValue() + /// + /// Attribute value to be set + protected internal virtual void SetValue(object value) { + this.PropertyInfo.SetValue(this.Node, value, null); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.SetValue() + /// GetValueFromString(strValue) will be processed by default before calling + /// + /// The value of the attribute string that needs to be set + protected internal virtual void SetValue(string strValue) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), null); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.SetValue() + /// GetValueFromBytes(byte[]) will be processed by default before calling + /// + /// Attribute binary data to be set + protected internal virtual void SetValue(byte[] byData) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), null); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.SetValue() + /// + /// Attribute value to be set + /// The optional index value of the indexed attribute should be null for non-indexed attributes + protected internal virtual void SetValue(object value, object[] index) { + this.PropertyInfo.SetValue(this.Node, value, index); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.SetValue() + /// GetValueFromString(strValue) will be processed by default before calling + /// + /// The value of the attribute string that needs to be set + /// The optional index value of the indexed attribute should be null for non-indexed attributes + protected internal virtual void SetValue(string strValue, object[] index) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), index); + } + /// + /// This function corresponds to System.Reflection.PropertyInfo.SetValue() + /// GetValueFromBytes(byte[]) will be processed by default before calling + /// + /// Attribute binary data to be set + /// The optional index value of the indexed attribute should be null for non-indexed attributes + protected internal virtual void SetValue(byte[] byData, object[] index) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), index); + } + /// + /// Occurs when there is an error setting the property value + /// + /// Exception information + protected internal virtual void OnSetValueError(Exception ex) { + this.Control.SetErrorMessage(ex.Message); + } + /// + /// Occurs when drawing the property's value area on the property editor + /// + /// Drawing tool + protected internal virtual void OnDrawValueRectangle(DrawingTools dt) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + STNodePropertyGrid ctrl = this.Control; + //STNodePropertyItem item = this._PropertyItem; + brush.Color = ctrl.ItemValueBackColor; + + g.FillRectangle(brush, this.RectangleR); + Rectangle rect = this.RectangleR; + rect.Width--; rect.Height--; + brush.Color = this.Control.ForeColor; + g.DrawString(this.GetStringFromValue(), ctrl.Font, brush, this.RectangleR, m_sf); + + if (this.PropertyInfo.PropertyType.IsEnum || this.PropertyInfo.PropertyType == m_t_bool) { + g.FillPolygon(Brushes.Gray, new Point[]{ + new Point(rect.Right - 13, rect.Top + rect.Height / 2 - 2), + new Point(rect.Right - 4, rect.Top + rect.Height / 2 - 2), + new Point(rect.Right - 9, rect.Top + rect.Height / 2 + 3) + }); + } + } + /// + /// Occurs when the mouse enters the area where the property value is located + /// + /// Event parameters + protected internal virtual void OnMouseEnter(EventArgs e) { } + /// + /// Occurs when the mouse is clicked in the area where the property value is located + /// + /// Event parameters + protected internal virtual void OnMouseDown(MouseEventArgs e) { + } + /// + /// Occurs when the mouse moves in the area where the property value is located + /// + /// Event parameters + protected internal virtual void OnMouseMove(MouseEventArgs e) { } + /// + /// Occurs when the mouse is raised in the area where the property value is located + /// + /// Event parameters + protected internal virtual void OnMouseUp(MouseEventArgs e) { } + /// + /// Occurs when the mouse leaves the area where the property value is located + /// + /// Event parameters + protected internal virtual void OnMouseLeave(EventArgs e) { } + /// + /// Occurs when the mouse is clicked in the area where the property value is located + /// + /// Event parameters + protected internal virtual void OnMouseClick(MouseEventArgs e) { + Type t = this.PropertyInfo.PropertyType; + if (t == m_t_bool || t.IsEnum) { + new FrmSTNodePropertySelect(this).Show(this.Control); + return; + } + Rectangle rect = this.Control.RectangleToScreen(this.RectangleR); + new FrmSTNodePropertyInput(this).Show(this.Control); + } + /// + /// Redraw the options area + /// + public void Invalidate() { + Rectangle rect = this.Rectangle; + rect.X -= this.Control.ScrollOffset; + this.Control.Invalidate(rect); + } + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodePropertyGrid.cs b/CodeWalker.WinForms/STNodeEditor/STNodePropertyGrid.cs new file mode 100644 index 0000000..58e80ea --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodePropertyGrid.cs @@ -0,0 +1,860 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; +using System.ComponentModel; +/* +MIT License + +Copyright (c) 2021 DebugST@crystal_lz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/* + * create: 2021-01-28 + * modify: 2021-03-02 + * Author: Crystal_lz + * blog: http://st233.com + * Gitee: https://gitee.com/DebugST + * Github: https://github.com/DebugST + */ +namespace ST.Library.UI.NodeEditor +{ + /// + /// STNode node attribute editor + /// + public class STNodePropertyGrid : Control + { + #region properties ========== + + private STNode _STNode; + /// + /// The currently displayed STNode + /// + [Description("Currently displayed STNode"), Browsable(false)] + public STNode STNode { + get { return _STNode; } + } + + private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125); + /// + /// Gets or sets the background color when the property option is hovered by the mouse + /// + [Description("Get or set the background color when the property option is hovered by the mouse")] + public Color ItemHoverColor { + get { return _ItemHoverColor; } + set { _ItemHoverColor = value; } + } + + private Color _ItemSelectedColor = Color.DodgerBlue; + /// + /// Gets or sets the background color when the property option is selected. This property cannot be set when AutoColor is set + /// + [Description("Background color when get or set property option is selected and this property cannot be set when AutoColor is set"), DefaultValue(typeof(Color), "DodgerBlue")] + public Color ItemSelectedColor { + get { return _ItemSelectedColor; } + set { + if (this._AutoColor) return; + if (value == _ItemSelectedColor) return; + _ItemSelectedColor = value; + this.Invalidate(); + } + } + + private Color _ItemValueBackColor = Color.FromArgb(255, 80, 80, 80); + /// + /// Gets or sets the background color of the attribute option value + /// + [Description("Get or set attribute option value background color")] + public Color ItemValueBackColor { + get { return _ItemValueBackColor; } + set { + _ItemValueBackColor = value; + this.Invalidate(); + } + } + + private Color _TitleColor = Color.FromArgb(127, 0, 0, 0); + /// + /// Gets or sets the default title background color + /// + [Description("Get or set the default title background color")] + public Color TitleColor { + get { return _TitleColor; } + set { + _TitleColor = value; + if (!this._ShowTitle) return; + this.Invalidate(m_rect_title); + } + } + + private Color _ErrorColor = Color.FromArgb(200, Color.Brown); + /// + /// Get or set the background color of the prompt message when the property is set incorrectly + /// + [Description("Get or set the background color of the message when the property is set incorrectly")] + public Color ErrorColor { + get { return _ErrorColor; } + set { _ErrorColor = value; } + } + + private Color _DescriptionColor = Color.FromArgb(200, Color.DarkGoldenrod); + /// + /// Gets or sets the background color of the attribute description information + /// + [Description("Get or set the background color of attribute description information")] + public Color DescriptionColor { + get { return _DescriptionColor; } + set { _DescriptionColor = value; } + } + + private bool _ShowTitle = true; + /// + /// Gets or sets whether to display the node title + /// + [Description("Get or set whether to display the node title")] + public bool ShowTitle { + get { return _ShowTitle; } + set { + _ShowTitle = value; + this.SetItemRectangle(); + this.Invalidate(); + } + } + + private bool _AutoColor = true; + /// + /// Get or set whether to automatically set the control highlight color according to STNode + /// + [Description("Get or set whether to automatically set the control highlight color according to STNode"), DefaultValue(true)] + public bool AutoColor { + get { return _AutoColor; } + set { _AutoColor = value; } + } + + private bool _InfoFirstOnDraw; + /// + /// Gets or whether the information panel is drawn first when the node is set + /// + [Description("Get or set whether to draw the information panel first when the node is set"), DefaultValue(false)] + public bool InfoFirstOnDraw { + get { return _InfoFirstOnDraw; } + set { _InfoFirstOnDraw = value; } + } + + private bool _ReadOnlyModel; + /// + /// Gets or sets whether the current property editor is in read-only mode + /// + [Description("Get or set whether the current property editor is in read-only mode"), DefaultValue(false)] + public bool ReadOnlyModel { + get { return _ReadOnlyModel; } + set { + if (value == _ReadOnlyModel) return; + _ReadOnlyModel = value; + this.Invalidate(m_rect_title); + } + } + /// + /// Get the current scroll bar height + /// + [Description("Get the current scroll bar height")] + public int ScrollOffset { get { return m_nOffsetY; } } + + #endregion + + #region protected fields ========== + + /// + /// Author link address area + /// + protected Rectangle m_rect_link; + /// + /// View the help button area + /// + protected Rectangle m_rect_help; + /// + /// Editor title area + /// + protected Rectangle m_rect_title; + /// + /// Panel toggle button area + /// + protected Rectangle m_rect_switch; + + /// + /// The vertical scroll offset used by the control during drawing + /// + protected int m_nOffsetY; + /// + /// Saved info panel vertical scroll offset + /// + protected int m_nInfoOffsetY; + /// + /// Saved property panel vertical scroll offset + /// + protected int m_nPropertyOffsetY; + + /// + /// The total height of the drawing area used by the control during drawing + /// + protected int m_nVHeight; + /// + /// The total height required for the saved info panel + /// + protected int m_nInfoVHeight; + /// + /// The total height required by the saved property panel + /// + protected int m_nPropertyVHeight; + /// + /// Key in the information panel displays the required horizontal width + /// + protected int m_nInfoLeft; + + #endregion + + private Type m_type; + private string[] m_KeysString = new string[] { "author", "email", "link", "view help" }; + + private int m_nTitleHeight = 20; + private int m_item_height = 30; + private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0); + private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255); + //All property lists are saved in this List + private List m_lst_item = new List(); + + private STNodePropertyDescriptor m_item_hover; //The currently hovered option + private STNodePropertyDescriptor m_item_hover_value; //The current value area is hovered by the mouse + private STNodePropertyDescriptor m_item_down_value; //The option that the current value area is clicked by the mouse + private STNodePropertyDescriptor m_item_selected; //The currently selected option + private STNodeAttribute m_node_attribute; //Node parameter information + private bool m_b_hover_switch; //Whether the mouse hovers over the panel switch button + private bool m_b_current_draw_info; //The current drawing is the information panel + + private Point m_pt_move; //The real-time coordinates of the mouse on the control + private Point m_pt_down; //The coordinates of the last mouse click on the control + private string m_str_err; // draw error message when set + private string m_str_desc; //Draw description information when set + + private Pen m_pen; + private SolidBrush m_brush; + private StringFormat m_sf; + private DrawingTools m_dt; + + /// + /// Construct a node attribute editor + /// + public STNodePropertyGrid() { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + m_pen = new Pen(Color.Black, 1); + m_brush = new SolidBrush(Color.Black); + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + m_dt.Pen = m_pen; + m_dt.SolidBrush = m_brush; + + this.ForeColor = Color.White; + this.BackColor = Color.FromArgb(255, 35, 35, 35); + + this.MinimumSize = new Size(120, 50); + this.Size = new Size(200, 150); + } + + #region private method ========== + + private List GetProperties(STNode node) { + List lst = new List(); + if (node == null) return lst; + Type t = node.GetType(); + foreach (var p in t.GetProperties()) { + var attrs = p.GetCustomAttributes(true); + foreach (var a in attrs) { + if (!(a is STNodePropertyAttribute)) continue; + var attr = a as STNodePropertyAttribute; + object obj = Activator.CreateInstance(attr.DescriptorType); + if (!(obj is STNodePropertyDescriptor)) + throw new ArgumentException("[STNodePropertyAttribute.DescriptorType] parameter value must be the type of [STNodePropertyDescriptor] or its subclass"); + var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType); + desc.Node = node; + desc.Name = attr.Name; + desc.Description = attr.Description; + desc.PropertyInfo = p; + desc.Control = this; + lst.Add(desc); + } + } + return lst; + } + + private STNodeAttribute GetNodeAttribute(STNode node) { + if (node == null) return null; + Type t = node.GetType(); + foreach (var v in t.GetCustomAttributes(true)) { + if (!(v is STNodeAttribute)) continue; + return (STNodeAttribute)v; + } + return null; + } + + private void SetItemRectangle() { + int nw_p = 0, nw_h = 0; + using (Graphics g = this.CreateGraphics()) { + foreach (var v in m_lst_item) { + SizeF szf = g.MeasureString(v.Name, this.Font); + if (szf.Width > nw_p) nw_p = (int)Math.Ceiling(szf.Width); + } + for (int i = 0; i < m_KeysString.Length - 1; i++) { + SizeF szf = g.MeasureString(m_KeysString[i], this.Font); + if (szf.Width > nw_h) nw_h = (int)Math.Ceiling(szf.Width); + } + nw_p += 5; nw_h += 5; + nw_p = Math.Min(nw_p, this.Width >> 1); + m_nInfoLeft = Math.Min(nw_h, this.Width >> 1); + + int nTitleHeight = this._ShowTitle ? m_nTitleHeight : 0; + for (int i = 0; i < m_lst_item.Count; i++) { + STNodePropertyDescriptor item = m_lst_item[i]; + Rectangle rect = new Rectangle(0, i * m_item_height + nTitleHeight, this.Width, m_item_height); + item.Rectangle = rect; + rect.Width = nw_p; + item.RectangleL = rect; + rect.X = rect.Right; + rect.Width = this.Width - rect.Left - 1; + rect.Inflate(-4, -4); + item.RectangleR = rect; + item.OnSetItemLocation(); + } + m_nPropertyVHeight = m_lst_item.Count * m_item_height; + if (this._ShowTitle) m_nPropertyVHeight += m_nTitleHeight; + } + } + + #endregion + + #region override ========== + + /// + /// Occurs when the control is redrawn + /// + /// Event parameters + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + m_dt.Graphics = g; + + m_nOffsetY = m_b_current_draw_info ? m_nInfoOffsetY : m_nPropertyOffsetY; + g.TranslateTransform(0, m_nOffsetY); + + if (m_b_current_draw_info) { + m_nVHeight = m_nInfoVHeight; + this.OnDrawInfo(m_dt); + } else { + m_nVHeight = m_nPropertyVHeight; + for (int i = 0; i < m_lst_item.Count; i++) { + this.OnDrawPropertyItem(m_dt, m_lst_item[i], i); + } + } + + g.ResetTransform(); + + if (this._ShowTitle) this.OnDrawTitle(m_dt); + m_sf.FormatFlags = 0; + if (!string.IsNullOrEmpty(m_str_err)) this.OnDrawErrorInfo(m_dt); + if (!string.IsNullOrEmpty(m_str_desc)) this.OnDrawDescription(m_dt); + } + /// + /// Occurs when the mouse moves over the control + /// + /// Event parameters + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + m_pt_move = e.Location; + bool bHover = this._ShowTitle && m_rect_switch.Contains(e.Location); + if (bHover != m_b_hover_switch) { + m_b_hover_switch = bHover; + this.Invalidate(m_rect_switch); + } + Point pt = new Point(e.X, e.Y - (int)m_nOffsetY); + MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta); + if (m_b_current_draw_info) + this.OnProcessHelpMouseMove(mea); + else + this.OnProcessPropertyMouseMove(mea); + } + /// + /// Occurs when the mouse is clicked on the control + /// + /// Event parameters + protected override void OnMouseDown(MouseEventArgs e) { + base.OnMouseDown(e); + m_pt_down = e.Location; + this.Focus(); + bool bRedraw = false; + if (m_str_err != null) { + bRedraw = true; + m_str_err = null; + } + if (this._ShowTitle) { + if (m_rect_switch.Contains(e.Location)) { + if (m_node_attribute == null || m_lst_item.Count == 0) return; + m_b_current_draw_info = !m_b_current_draw_info; + this.Invalidate(); + return; + } else if (m_rect_title.Contains(e.Location)) { + return; + } + } + if (this._ShowTitle && m_rect_switch.Contains(e.Location)) { + if (m_node_attribute == null || m_lst_item.Count == 0) return; + m_b_current_draw_info = !m_b_current_draw_info; + this.Invalidate(); + return; + } + Point pt = new Point(e.X, e.Y - (int)m_nOffsetY); + MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta); + + if (m_b_current_draw_info) + this.OnProcessInfoMouseDown(mea); + else + this.OnProcessPropertyMouseDown(mea); + if (bRedraw) this.Invalidate(); + } + /// + /// Occurs when the mouse is raised over the control + /// + /// Event parameters + protected override void OnMouseUp(MouseEventArgs e) { + base.OnMouseUp(e); + m_str_desc = null; + if (m_item_down_value != null && !this._ReadOnlyModel) { + Point pt = new Point(e.X, e.Y - (int)m_nOffsetY); + MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta); + m_item_down_value.OnMouseUp(mea); + if (m_pt_down == e.Location && !this._ReadOnlyModel) { + m_item_down_value.OnMouseClick(mea); + } + } + m_item_down_value = null; + this.Invalidate(); + } + /// + /// Occurs when the mouse leaves the control + /// + /// Event parameters + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + m_b_hover_switch = false; + if (m_item_hover_value != null && !this._ReadOnlyModel) m_item_hover_value.OnMouseLeave(e); + m_item_hover = null; + this.Invalidate(); + } + /// + /// Occurs when the mouse rolls the wheel on the control + /// + /// Event parameters + protected override void OnMouseWheel(MouseEventArgs e) { + base.OnMouseWheel(e); + if (e.Delta > 0) { + if (m_nOffsetY == 0) return; + m_nOffsetY += m_item_height; + if (m_nOffsetY > 0) m_nOffsetY = 0; + } else { + if (this.Height - m_nOffsetY >= m_nVHeight) return; + m_nOffsetY -= m_item_height; + } + if (m_b_current_draw_info) + m_nInfoOffsetY = m_nOffsetY; + else + m_nPropertyOffsetY = m_nOffsetY; + this.Invalidate(); + } + /// + /// Occurs when the control rectangle area is set + /// + /// x coordinate + /// y coordinate + /// width + /// height + /// Specify the identifier to be set + //protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) { + // if (width < 120) width = 120; + // if (height < 50) height = 50; + // base.SetBoundsCore(x, y, width, height, specified); + //} + /// + /// Occurs when the control size changes + /// + /// Event parameters + protected override void OnResize(EventArgs e) { + base.OnResize(e); + m_rect_title.Width = this.Width; + m_rect_title.Height = m_nTitleHeight; + if (this._ShowTitle) + m_rect_switch = new Rectangle(this.Width - m_nTitleHeight + 2, 2, m_nTitleHeight - 4, m_nTitleHeight - 4); + if (this._STNode != null) this.SetItemRectangle(); + } + + #endregion + + #region virtual method ========== + + /// + /// Occurs when drawing attribute options + /// + /// Drawing tool + /// Target attribute option descriptor + /// The index of the option + protected virtual void OnDrawPropertyItem(DrawingTools dt, STNodePropertyDescriptor item, int nIndex) { + Graphics g = dt.Graphics; + m_brush.Color = (nIndex % 2 == 0) ? m_clr_item_1 : m_clr_item_2; + g.FillRectangle(m_brush, item.Rectangle); + if (item == m_item_hover || item == m_item_selected) { + m_brush.Color = this._ItemHoverColor; + g.FillRectangle(m_brush, item.Rectangle); + } + if (m_item_selected == item) { + g.FillRectangle(m_brush, item.Rectangle.X, item.Rectangle.Y, 5, item.Rectangle.Height); + if (this._AutoColor && this._STNode != null) + m_brush.Color = this._STNode.TitleColor; + else + m_brush.Color = this._ItemSelectedColor; + g.FillRectangle(m_brush, item.Rectangle.X, item.Rectangle.Y + 4, 5, item.Rectangle.Height - 8); + } + m_sf.Alignment = StringAlignment.Far; + m_brush.Color = this.ForeColor; + g.DrawString(item.Name, this.Font, m_brush, item.RectangleL, m_sf); + + item.OnDrawValueRectangle(m_dt); + if (this._ReadOnlyModel) { + m_brush.Color = Color.FromArgb(125, 125, 125, 125); + g.FillRectangle(m_brush, item.RectangleR); + m_pen.Color = this.ForeColor; + //g.DrawLine(m_pen, + // item.RectangleR.Left - 2, item.RectangleR.Top + item.RectangleR.Height / 2, + // item.RectangleR.Right + 1, item.RectangleR.Top + item.RectangleR.Height / 2); + } + } + /// + /// Draw the property window title + /// + /// Drawing tool + protected virtual void OnDrawTitle(DrawingTools dt) { + Graphics g = dt.Graphics; + if (this._AutoColor) + m_brush.Color = this._STNode == null ? this._TitleColor : this._STNode.TitleColor; + else + m_brush.Color = this._TitleColor; + g.FillRectangle(m_brush, m_rect_title); + m_brush.Color = this._STNode == null ? this.ForeColor : this._STNode.ForeColor; + m_sf.Alignment = StringAlignment.Center; + g.DrawString(this._STNode == null ? this.Text : this._STNode.Title, this.Font, m_brush, m_rect_title, m_sf); + + if (this._ReadOnlyModel) { + m_brush.Color = this.ForeColor; + g.FillRectangle(dt.SolidBrush, 4, 5, 2, 4); + g.FillRectangle(dt.SolidBrush, 6, 5, 2, 2); + g.FillRectangle(dt.SolidBrush, 8, 5, 2, 4); + g.FillRectangle(dt.SolidBrush, 3, 9, 8, 6); + } + //Whether to draw the panel switch button + if (m_node_attribute == null || m_lst_item.Count == 0) return; + if (m_b_hover_switch) { + m_brush.Color = this.BackColor; + g.FillRectangle(m_brush, m_rect_switch); + } + m_pen.Color = this._STNode == null ? this.ForeColor : this._STNode.ForeColor; + m_brush.Color = m_pen.Color; + int nT1 = m_rect_switch.Top + m_rect_switch.Height / 2 - 2; + int nT2 = m_rect_switch.Top + m_rect_switch.Height / 2 + 1; + g.DrawRectangle(m_pen, m_rect_switch.Left, m_rect_switch.Top, m_rect_switch.Width - 1, m_rect_switch.Height - 1); + + g.DrawLines(m_pen, new Point[]{ + new Point(m_rect_switch.Left + 2, nT1), new Point(m_rect_switch.Right - 3, nT1), + new Point(m_rect_switch.Left + 3, nT1 - 1), new Point(m_rect_switch.Right - 3, nT1 - 1) + }); + g.DrawLines(m_pen, new Point[]{ + new Point(m_rect_switch.Left + 2, nT2), new Point(m_rect_switch.Right - 3, nT2), + new Point(m_rect_switch.Left + 2, nT2 + 1), new Point(m_rect_switch.Right - 4, nT2 + 1), + }); + + g.FillPolygon(m_brush, new Point[]{ + new Point(m_rect_switch.Left + 2, nT1), + new Point(m_rect_switch.Left + 7, nT1), + new Point(m_rect_switch.Left + 7, m_rect_switch.Top ), + }); + g.FillPolygon(m_brush, new Point[]{ + new Point(m_rect_switch.Right - 2, nT2), + new Point(m_rect_switch.Right - 7, nT2), + new Point(m_rect_switch.Right - 7, m_rect_switch.Bottom - 2 ), + }); + } + /// + /// Occurs when the attribute description information needs to be drawn + /// + /// Drawing tool + protected virtual void OnDrawDescription(DrawingTools dt) { + if (string.IsNullOrEmpty(m_str_desc)) return; + Graphics g = dt.Graphics; + SizeF szf = g.MeasureString(m_str_desc, this.Font, this.Width - 4); + Rectangle rect_desc = new Rectangle(0, this.Height - (int)szf.Height - 4, this.Width, (int)szf.Height + 4); + m_brush.Color = this._DescriptionColor; + g.FillRectangle(m_brush, rect_desc); + m_pen.Color = this._DescriptionColor; + g.DrawRectangle(m_pen, 0, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1); + rect_desc.Inflate(-4, 0); + m_brush.Color = this.ForeColor; + m_sf.Alignment = StringAlignment.Near; + g.DrawString(m_str_desc, this.Font, m_brush, rect_desc, m_sf); + } + /// + /// Occurs when an error message needs to be drawn + /// + /// Drawing tool + protected virtual void OnDrawErrorInfo(DrawingTools dt) { + if (string.IsNullOrEmpty(m_str_err)) return; + Graphics g = dt.Graphics; + SizeF szf = g.MeasureString(m_str_err, this.Font, this.Width - 4); + Rectangle rect_desc = new Rectangle(0, 0, this.Width, (int)szf.Height + 4); + m_brush.Color = this._ErrorColor; + g.FillRectangle(m_brush, rect_desc); + m_pen.Color = this._ErrorColor; + g.DrawRectangle(m_pen, 0, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1); + rect_desc.Inflate(-4, 0); + m_brush.Color = this.ForeColor; + m_sf.Alignment = StringAlignment.Near; + g.DrawString(m_str_err, this.Font, m_brush, rect_desc, m_sf); + } + /// + /// Occurs when drawing node information + /// + /// Drawing tool + protected virtual void OnDrawInfo(DrawingTools dt) { + if (m_node_attribute == null) return; + var attr = m_node_attribute; + Graphics g = dt.Graphics; + Color clr_r = Color.FromArgb(this.ForeColor.A / 2, this.ForeColor); + m_sf.Alignment = StringAlignment.Near; + Rectangle rect = new Rectangle(0, this._ShowTitle ? m_nTitleHeight : 0, this.Width, m_item_height); + Rectangle rect_l = new Rectangle(2, rect.Top, m_nInfoLeft - 2, m_item_height); + Rectangle rect_r = new Rectangle(m_nInfoLeft, rect.Top, this.Width - m_nInfoLeft, m_item_height); + m_brush.Color = m_clr_item_2; + g.FillRectangle(m_brush, rect); + m_brush.Color = this.ForeColor; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + m_sf.Alignment = StringAlignment.Near; + g.DrawString(m_KeysString[0], this.Font, m_brush, rect_l, m_sf); //author + m_brush.Color = clr_r; + g.DrawString(attr.Author, this.Font, m_brush, rect_r, m_sf); + rect.Y += m_item_height; rect_l.Y += m_item_height; rect_r.Y += m_item_height; + + m_brush.Color = m_clr_item_1; + g.FillRectangle(m_brush, rect); + m_brush.Color = this.ForeColor; + g.DrawString(m_KeysString[1], this.Font, m_brush, rect_l, m_sf); //mail + m_brush.Color = clr_r; + g.DrawString(attr.Mail, this.Font, m_brush, rect_r, m_sf); + rect.Y += m_item_height; rect_l.Y += m_item_height; rect_r.Y += m_item_height; + + m_brush.Color = m_clr_item_2; + g.FillRectangle(m_brush, rect); + m_brush.Color = this.ForeColor; + g.DrawString(m_KeysString[2], this.Font, m_brush, rect_l, m_sf); //link_key + m_brush.Color = clr_r; + g.DrawString(attr.Link, this.Font, Brushes.CornflowerBlue, rect_r, m_sf); //link + if (!string.IsNullOrEmpty(attr.Link)) m_rect_link = rect_r; + //fill left + m_brush.Color = Color.FromArgb(40, 125, 125, 125); + g.FillRectangle(m_brush, 0, this._ShowTitle ? m_nTitleHeight : 0, m_nInfoLeft - 1, m_item_height * 3); + + rect.X = 5; rect.Y += m_item_height; + rect.Width = this.Width - 10; + if (!string.IsNullOrEmpty(m_node_attribute.Description)) { + float h = g.MeasureString(m_node_attribute.Description, this.Font, rect.Width).Height; + rect.Height = (int)Math.Ceiling(h / m_item_height) * m_item_height; + m_brush.Color = clr_r; + m_sf.FormatFlags = 0; + g.DrawString(m_node_attribute.Description, this.Font, m_brush, rect, m_sf); + } + m_nInfoVHeight = rect.Bottom; + bool bHasHelp = STNodeAttribute.GetHelpMethod(m_type) != null; + rect.X = 5; rect.Y += rect.Height; + rect.Height = m_item_height; + m_sf.Alignment = StringAlignment.Center; + m_brush.Color = Color.FromArgb(125, 125, 125, 125); + g.FillRectangle(m_brush, rect); + if (bHasHelp) m_brush.Color = Color.CornflowerBlue; + g.DrawString(m_KeysString[3], this.Font, m_brush, rect, m_sf); + if (bHasHelp) m_rect_help = rect; + else { + int w = (int)g.MeasureString(m_KeysString[3], this.Font).Width + 1; + int x = rect.X + (rect.Width - w) / 2, y = rect.Y + rect.Height / 2; + m_pen.Color = m_brush.Color; + g.DrawLine(m_pen, x, y, x + w, y); + } + m_nInfoVHeight = rect.Bottom; + } + /// + /// Occurs when the mouse is clicked on the property panel + /// + /// Mouse event parameters + protected virtual void OnProcessPropertyMouseDown(MouseEventArgs e) { + bool bRedraw = false; + if (m_item_selected != m_item_hover) { + m_item_selected = m_item_hover; + bRedraw = true; + } + m_item_down_value = null; + if (m_item_selected == null) { + if (bRedraw) this.Invalidate(); + return; + } + if (m_item_selected.RectangleR.Contains(e.Location)) { + m_item_down_value = m_item_selected; + if (!this._ReadOnlyModel) + m_item_selected.OnMouseDown(e); + else { + return; + } + } else if (m_item_selected.RectangleL.Contains(e.Location)) { + m_str_desc = m_item_selected.Description; + bRedraw = true; + } + if (bRedraw) this.Invalidate(); + } + /// + /// Occurs when the mouse is clicked in the info panel + /// + /// Mouse event parameters + protected virtual void OnProcessInfoMouseDown(MouseEventArgs e) { + try { + if (m_rect_link.Contains(e.Location)) { + System.Diagnostics.Process.Start(m_node_attribute.Link); + } else if (m_rect_help.Contains(e.Location)) { + STNodeAttribute.ShowHelp(m_type); + } + } catch (Exception ex) { + this.SetErrorMessage(ex.Message); + } + } + /// + /// Occurs when the mouse is moved in the properties panel + /// + /// Mouse event parameters + protected virtual void OnProcessPropertyMouseMove(MouseEventArgs e) { + if (m_item_down_value != null) { + m_item_down_value.OnMouseMove(e); + return; + } + STNodePropertyDescriptor item = null; + foreach (var v in m_lst_item) { + if (v.Rectangle.Contains(e.Location)) { + item = v; + break; + } + } + if (item != null) { + if (item.RectangleR.Contains(e.Location)) { + if (m_item_hover_value != item) { + if (m_item_hover_value != null) m_item_hover_value.OnMouseLeave(e); + m_item_hover_value = item; + m_item_hover_value.OnMouseEnter(e); + } + m_item_hover_value.OnMouseMove(e); + } else { + if (m_item_hover_value != null) m_item_hover_value.OnMouseLeave(e); + } + } + if (m_item_hover != item) { + m_item_hover = item; + this.Invalidate(); + } + } + /// + /// Occurs when the mouse is moved in the info panel + /// + /// Mouse event parameters + protected virtual void OnProcessHelpMouseMove(MouseEventArgs e) { + if (m_rect_link.Contains(e.Location) || m_rect_help.Contains(e.Location)) { + this.Cursor = Cursors.Hand; + } else this.Cursor = Cursors.Arrow; + } + + #endregion + + #region public ========== + + /// + /// Set the STNode node that needs to be displayed + /// + /// target node + public void SetNode(STNode node) { + if (node == this._STNode) return; + m_nInfoOffsetY = m_nPropertyOffsetY = 0; + m_nInfoVHeight = m_nPropertyVHeight = 0; + m_rect_link = m_rect_help = Rectangle.Empty; + m_str_desc = m_str_err = null; + this._STNode = node; + if (node != null) { + m_type = node.GetType(); + m_lst_item = this.GetProperties(node); + m_node_attribute = this.GetNodeAttribute(node); + this.SetItemRectangle(); + m_b_current_draw_info = m_lst_item.Count == 0 || this._InfoFirstOnDraw; + if (this._AutoColor) this._ItemSelectedColor = this._STNode.TitleColor; + } else { + m_type = null; + m_lst_item.Clear(); + m_node_attribute = null; + } + this.Invalidate(); + } + /// + /// Set the display text of the Key of the information page + /// + /// author + /// mail + /// connect + /// View help + public void SetInfoKey(string strAuthor, string strMail, string strLink, string strHelp) { + m_KeysString = new string[] { strAuthor, strMail, strLink, strHelp }; + } + /// + /// Set the error message to display + /// + /// Error message + public void SetErrorMessage(string strText) { + m_str_err = strText; + this.Invalidate(); + } + + #endregion + } +} \ No newline at end of file diff --git a/CodeWalker.WinForms/STNodeEditor/STNodeTreeView.cs b/CodeWalker.WinForms/STNodeEditor/STNodeTreeView.cs new file mode 100644 index 0000000..bab7adc --- /dev/null +++ b/CodeWalker.WinForms/STNodeEditor/STNodeTreeView.cs @@ -0,0 +1,907 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; +using System.ComponentModel; +using System.Collections; +/* +MIT License + +Copyright (c) 2021 DebugST@crystal_lz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/* + * create: 2021-02-23 + * modify: 2021-03-02 + * Author: Crystal_lz + * blog: http://st233.com + * Gitee: https://gitee.com/DebugST + * Github: https://github.com/DebugST + */ +namespace ST.Library.UI.NodeEditor +{ + public class STNodeTreeView : Control + { + private Color _ItemBackColor = Color.FromArgb(255, 45, 45, 45); + /// + /// Get or set the background color of each row attribute option + /// + [Description("Get or set the background color of each row attribute option")] + public Color ItemBackColor { + get { return _ItemBackColor; } + set { + _ItemBackColor = value; + } + } + + private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125); + /// + /// Gets or sets the background color when the property option is hovered by the mouse + /// + [Description("Get or set the background color when the property option is hovered by the mouse")] + public Color ItemHoverColor { + get { return _ItemHoverColor; } + set { _ItemHoverColor = value; } + } + + private Color _TitleColor = Color.FromArgb(255, 60, 60, 60); + /// + /// Get or set the background color of the top retrieval area + /// + [Description("Get or set the background color of the top retrieval area")] + public Color TitleColor { + get { return _TitleColor; } + set { + _TitleColor = value; + this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight)); + } + } + + /// + /// Gets or sets the background color of the search text box + /// + [Description("Get or set the background color of the search text box")] + public Color TextBoxColor { + get { return m_tbx.BackColor; } + set { + m_tbx.BackColor = value; + this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight)); + } + } + + private Color _HightLightTextColor = Color.Lime; + /// + /// Gets or sets the highlighted text color when retrieving + /// + [Description("Get or set the highlighted text color during retrieval"), DefaultValue(typeof(Color), "Lime")] + public Color HightLightTextColor { + get { return _HightLightTextColor; } + set { _HightLightTextColor = value; } + } + + private Color _InfoButtonColor = Color.Gray; + /// + /// Gets or sets the color of the information display button. If AutoColor is set, this property value cannot be set + /// + [Description("Get or set the color of the information display button. If AutoColor is set, this property value cannot be set"), DefaultValue(typeof(Color), "Gray")] + public Color InfoButtonColor { + get { return _InfoButtonColor; } + set { _InfoButtonColor = value; } + } + + private Color _FolderCountColor = Color.FromArgb(40, 255, 255, 255); + /// + /// Get or set the text color of the count + /// + [Description("Get or set the text color of statistics")] + public Color FolderCountColor { + get { return _FolderCountColor; } + set { _FolderCountColor = value; } + } + + private Color _SwitchColor = Color.LightGray; + + private bool _ShowFolderCount = true; + /// + /// Get or set whether to count the number of STNodes + /// + [Description("Get or set whether to count the number of STNodes"), DefaultValue(typeof(Color), "LightGray")] + public bool ShowFolderCount { + get { return _ShowFolderCount; } + set { _ShowFolderCount = value; } + } + + private bool _ShowInfoButton = true; + /// + /// Gets or sets whether to display the information button + /// + [Description("Get or set whether to display the information button"), DefaultValue(true)] + public bool ShowInfoButton { + get { return _ShowInfoButton; } + set { _ShowInfoButton = value; } + } + + private bool _InfoPanelIsLeftLayout = true; + /// + /// Gets or sets whether the preview window is laid out to the left + /// + [Description("Get or set whether the preview window is a left layout"), DefaultValue(true)] + public bool InfoPanelIsLeftLayout { + get { return _InfoPanelIsLeftLayout; } + set { _InfoPanelIsLeftLayout = value; } + } + + private bool _AutoColor = true; + /// + /// Get or set the title color of the STNode corresponding to some colors in the control + /// + [Description("Get or set the title color of STNode corresponding to some colors in the control"), DefaultValue(true)] + public bool AutoColor { + get { return _AutoColor; } + set { + _AutoColor = value; + this.Invalidate(); + } + } + + private STNodeEditor _Editor; + /// + /// STNodeEditor used to get the node preview + /// + [Description("STNodeEditor used when getting node preview"), Browsable(false)] + public STNodeEditor Editor { + get { return _Editor; } + } + + private STNodePropertyGrid _PropertyGrid; + /// + /// Get the STNodePropertyGrid used when getting the node preview + /// + [Description("STNodePropertyGrid used when getting node preview"), Browsable(false)] + public STNodePropertyGrid PropertyGrid { + get { return _PropertyGrid; } + } + + private int m_nItemHeight = 29; + + private static Type m_type_node_base = typeof(STNode); + private static char[] m_chr_splitter = new char[] { '/', '\\' }; + private STNodeTreeCollection m_items_draw; + private STNodeTreeCollection m_items_source = new STNodeTreeCollection("ROOT"); + private Dictionary m_dic_all_type = new Dictionary(); + + private Pen m_pen; + private SolidBrush m_brush; + private StringFormat m_sf; + private DrawingTools m_dt; + private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0);// Color.FromArgb(255, 40, 40, 40); + private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255);// Color.FromArgb(255, 50, 50, 50); + + private int m_nOffsetY; //The vertical height that needs to be offset when the control is drawn + private int m_nSourceOffsetY; //The vertical height that needs to be offset when drawing source data + private int m_nSearchOffsetY; //The vertical height that needs to be offset when drawing and retrieving data + private int m_nVHeight; //The total height required by the content in the control + + private bool m_bHoverInfo; //Whether the mouse is currently hovering over the information display button + private STNodeTreeCollection m_item_hover; //The tree node currently hovered by the mouse + private Point m_pt_control; //The coordinates of the mouse on the control + private Point m_pt_offsety; //The coordinates of the mouse after the hammer is offset on the control + private Rectangle m_rect_clear; //Clear the search button area + + private string m_str_search; // retrieved text + private TextBox m_tbx = new TextBox(); //Retrieve the textbox + /// + /// Construct a STNode tree control + /// + public STNodeTreeView() { + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + this.MinimumSize = new System.Drawing.Size(100, 60); + this.Size = new System.Drawing.Size(200, 150); + m_items_draw = m_items_source; + m_pen = new Pen(Color.Black); + m_brush = new SolidBrush(Color.White); + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + m_dt.Pen = m_pen; + m_dt.SolidBrush = m_brush; + this.ForeColor = Color.FromArgb(255, 220, 220, 220); + this.BackColor = Color.FromArgb(255, 35, 35, 35); + m_tbx.Left = 6; + m_tbx.BackColor = Color.FromArgb(255, 30, 30, 30); + m_tbx.ForeColor = this.ForeColor; + m_tbx.BorderStyle = BorderStyle.None; + m_tbx.MaxLength = 20; + m_tbx.TextChanged += new EventHandler(m_tbx_TextChanged); + this.Controls.Add(m_tbx); + this.AllowDrop = true; + + this._Editor = new STNodeEditor(); + this._PropertyGrid = new STNodePropertyGrid(); + } + + #region private method ========== + + private void m_tbx_TextChanged(object sender, EventArgs e) { + m_str_search = m_tbx.Text.Trim().ToLower(); + m_nSearchOffsetY = 0; + if (m_str_search == string.Empty) { + m_items_draw = m_items_source; + this.Invalidate(); + return; + } + m_items_draw = m_items_source.Copy(); + this.Search(m_items_draw, new Stack(), m_str_search); + this.Invalidate(); + } + + private bool Search(STNodeTreeCollection items, Stack stack, string strText) { + bool bFound = false; + string[] strName = new string[items.Count]; + int nCounter = 0; + foreach (STNodeTreeCollection v in items) { + if (v.NameLower.IndexOf(strText) != -1) { + v.IsOpen = bFound = true; + } else { + if (!this.Search(v, stack, strText)) { + stack.Push(v.Name); + nCounter++; + } else { + v.IsOpen = bFound = true; + } + } + } + for (int i = 0; i < nCounter; i++) items.Remove(stack.Pop(), false); + return bFound; + } + + private bool AddSTNode(Type stNodeType, STNodeTreeCollection items, string strLibName, bool bShowException) { + if (m_dic_all_type.ContainsKey(stNodeType)) return false; + if (stNodeType == null) return false; + if (!stNodeType.IsSubclassOf(m_type_node_base)) { + if (bShowException) + throw new ArgumentException("Unsupported type [" + stNodeType.FullName + "] [stNodeType] parameter value must be the type of [STNode] subclass"); + else return false; + } + var attr = this.GetNodeAttribute(stNodeType); + if (attr == null) { + if (bShowException) + throw new InvalidOperationException("Type [" + stNodeType.FullName + "] is not marked by [STNodeAttribute]"); + else return false; + } + string strPath = string.Empty; + items.STNodeCount++; + if (!string.IsNullOrEmpty(attr.Path)) { + var strKeys = attr.Path.Split (m_chr_splitter); + for (int i = 0; i < strKeys.Length; i++) { + items = items.Add(strKeys[i]); + items.STNodeCount++; + strPath += "/" + strKeys[i]; + } + } + try { + STNode node = (STNode)Activator.CreateInstance(stNodeType); + STNodeTreeCollection stt = new STNodeTreeCollection(node.Title); + stt.Path = (strLibName + "/" + attr.Path).Trim('/'); + stt.STNodeType = stNodeType; + items[stt.Name] = stt; + stt.STNodeTypeColor = node.TitleColor; + m_dic_all_type.Add(stNodeType, stt.Path); + this.Invalidate(); + } catch (Exception ex) { + if (bShowException) throw ex; + return false; + } + return true; + } + + private STNodeTreeCollection AddAssemblyPrivate(string strFile) { + strFile = System.IO.Path.GetFullPath(strFile); + var asm = Assembly.LoadFrom(strFile); + STNodeTreeCollection items = new STNodeTreeCollection(System.IO.Path.GetFileNameWithoutExtension(strFile)); + foreach (var v in asm.GetTypes()) { + if (v.IsAbstract) continue; + if (v.IsSubclassOf(m_type_node_base)) this.AddSTNode(v, items, items.Name, false); + } + return items; + } + + private STNodeAttribute GetNodeAttribute(Type stNodeType) { + if (stNodeType == null) return null; + foreach (var v in stNodeType.GetCustomAttributes(true)) { + if (!(v is STNodeAttribute)) continue; + return (STNodeAttribute)v; + } + return null; + } + + private STNodeTreeCollection FindItemByPoint(STNodeTreeCollection items, Point pt) { + foreach (STNodeTreeCollection t in items) { + if (t.DisplayRectangle.Contains(pt)) return t; + if (t.IsOpen) { + var n = FindItemByPoint (t, pt); + if (n != null) return n; + } + } + return null; + } + + #endregion + + #region overide method ========== + + protected override void OnCreateControl() { + base.OnCreateControl(); + m_tbx.Top = (m_nItemHeight - m_tbx.Height) / 2; + } + + protected override void OnResize(EventArgs e) { + base.OnResize(e); + m_tbx.Width = this.Width - 29; + m_rect_clear = new Rectangle(this.Width - 20, 9, 12, 12); + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + m_nOffsetY = string.IsNullOrEmpty(m_str_search) ? m_nSourceOffsetY : m_nSearchOffsetY; + Graphics g = e.Graphics; + m_dt.Graphics = g; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + g.TranslateTransform(0, m_nOffsetY); + int nCounter = 0; + foreach (STNodeTreeCollection v in m_items_draw) + nCounter = this.OnStartDrawItem(m_dt, v, nCounter, 0); + m_nVHeight = (nCounter + 1) * m_nItemHeight; + foreach (STNodeTreeCollection v in m_items_draw) + this.OnDrawSwitch(m_dt, v); + g.ResetTransform(); + this.OnDrawSearch(m_dt); + } + + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + bool bRedraw = false; + m_pt_offsety = m_pt_control = e.Location; + m_pt_offsety.Y -= m_nOffsetY; + if (!string.IsNullOrEmpty(m_str_search) && m_rect_clear.Contains(e.Location)) + this.Cursor = Cursors.Hand; + else + this.Cursor = Cursors.Arrow; + var node = this.FindItemByPoint(m_items_draw, m_pt_offsety); + if (m_item_hover != node) { + m_item_hover = node; + bRedraw = true; + } + if (node != null) { + bool bHoverInfo = node.InfoRectangle.Contains(m_pt_offsety); + if (bHoverInfo != m_bHoverInfo) { + m_bHoverInfo = bHoverInfo; + bRedraw = true; + } + } + if (bRedraw) this.Invalidate(); + } + + protected override void OnMouseDown(MouseEventArgs e) { + base.OnMouseDown(e); + this.Focus(); + if (!string.IsNullOrEmpty(m_str_search) && m_rect_clear.Contains(e.Location)) { + m_tbx.Text = string.Empty; + return; + } + m_pt_offsety = m_pt_control = e.Location; + m_pt_offsety.Y -= m_nOffsetY; + if (m_item_hover == null) return; + if (m_item_hover.SwitchRectangle.Contains(m_pt_offsety)) { + m_item_hover.IsOpen = !m_item_hover.IsOpen; + this.Invalidate(); + } else if (m_item_hover.InfoRectangle.Contains(m_pt_offsety)) { + Rectangle rect = this.RectangleToScreen(m_item_hover.DisplayRectangle); + FrmNodePreviewPanel frm = new FrmNodePreviewPanel(m_item_hover.STNodeType, + new Point(rect.Right - m_nItemHeight, rect.Top + m_nOffsetY), + m_nItemHeight, + this._InfoPanelIsLeftLayout, + this._Editor, this._PropertyGrid); + frm.BackColor = this.BackColor; + frm.Show(this); + } else if (m_item_hover.STNodeType != null) { + DataObject d = new DataObject("STNodeType", m_item_hover.STNodeType); + this.DoDragDrop(d, DragDropEffects.Copy); + } + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) { + base.OnMouseDoubleClick(e); + m_pt_offsety = m_pt_control = e.Location; + m_pt_offsety.Y -= m_nOffsetY; + STNodeTreeCollection item = this.FindItemByPoint(m_items_draw, m_pt_offsety); + if (item == null || item.STNodeType != null) return; + item.IsOpen = !item.IsOpen; + this.Invalidate(); + } + + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + if (m_item_hover != null) { + m_item_hover = null; + this.Invalidate(); + } + } + + protected override void OnMouseWheel(MouseEventArgs e) { + base.OnMouseWheel(e); + if (e.Delta > 0) { + if (m_nOffsetY == 0) return; + m_nOffsetY += m_nItemHeight; + if (m_nOffsetY > 0) m_nOffsetY = 0; + } else { + if (this.Height - m_nOffsetY >= m_nVHeight) return; + m_nOffsetY -= m_nItemHeight; + } + if (string.IsNullOrEmpty(m_str_search)) + m_nSourceOffsetY = m_nOffsetY; + else + m_nSearchOffsetY = m_nOffsetY; + this.Invalidate(); + } + + #endregion + + #region protected method ========== + /// + /// Occurs when the retrieved text area is drawn + /// + /// Drawing tool + protected virtual void OnDrawSearch(DrawingTools dt) { + Graphics g = dt.Graphics; + m_brush.Color = this._TitleColor; + g.FillRectangle(m_brush, 0, 0, this.Width, m_nItemHeight); + m_brush.Color = m_tbx.BackColor; + g.FillRectangle(m_brush, 5, 5, this.Width - 10, m_nItemHeight - 10); + m_pen.Color = this.ForeColor; + if (string.IsNullOrEmpty(m_str_search)) { + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.DrawEllipse(m_pen, this.Width - 17, 8, 8, 8); + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + g.DrawLine(m_pen, this.Width - 13, 17, this.Width - 13, m_nItemHeight - 9); + } else { + m_pen.Color = this.ForeColor; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.DrawEllipse(m_pen, this.Width - 20, 9, 10, 10); + g.DrawLine(m_pen, this.Width - 18, 11, this.Width - 12, 17); + g.DrawLine(m_pen, this.Width - 12, 11, this.Width - 18, 17); + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + } + } + /// + /// Occurs when starting to draw each node of the tree node + /// + /// Drawing tool + /// The current collection that needs to be drawn + /// Counter of drawn counts + /// Currently located in the sub-collection of which level + /// Number of already drawn + protected virtual int OnStartDrawItem(DrawingTools dt, STNodeTreeCollection Items, int nCounter, int nLevel) { + Graphics g = dt.Graphics; + Items.DisplayRectangle = new Rectangle(0, m_nItemHeight * (nCounter + 1), this.Width, m_nItemHeight); + Items.SwitchRectangle = new Rectangle(5 + nLevel * 10, (nCounter + 1) * m_nItemHeight, 10, m_nItemHeight); + if (this._ShowInfoButton && Items.STNodeType != null) + Items.InfoRectangle = new Rectangle(this.Width - 18, Items.DisplayRectangle.Top + (m_nItemHeight - 14) / 2, 14, 14); + else Items.InfoRectangle = Rectangle.Empty; + this.OnDrawItem(dt, Items, nCounter++, nLevel); + if (!Items.IsOpen) return nCounter; + foreach (STNodeTreeCollection n in Items) { + if (n.STNodeType == null) + nCounter = this.OnStartDrawItem(dt, n, nCounter++, nLevel + 1); + } + foreach (STNodeTreeCollection n in Items) { + if (n.STNodeType != null) + nCounter = this.OnStartDrawItem(dt, n, nCounter++, nLevel + 1); + } + foreach (STNodeTreeCollection v in Items) this.OnDrawSwitch(dt, v); + return nCounter; + } + /// + /// Occurs when each node of the tree node is drawn + /// + /// Drawing tool + /// The current collection that needs to be drawn + /// Counter of drawn counts + /// Currently located in the sub-collection of which level + protected virtual void OnDrawItem(DrawingTools dt, STNodeTreeCollection items, int nCounter, int nLevel) { + Graphics g = dt.Graphics; + m_brush.Color = nCounter % 2 == 0 ? m_clr_item_1 : m_clr_item_2; + g.FillRectangle(m_brush, items.DisplayRectangle); + if (items == m_item_hover) { + m_brush.Color = this._ItemHoverColor; + g.FillRectangle(m_brush, items.DisplayRectangle); + } + Rectangle rect = new Rectangle(45 + nLevel * 10, items.SwitchRectangle.Top, this.Width - 45 - nLevel * 10, m_nItemHeight); + m_pen.Color = Color.FromArgb(100, 125, 125, 125); + g.DrawLine(m_pen, 9, items.SwitchRectangle.Top + m_nItemHeight / 2, items.SwitchRectangle.Left + 19, items.SwitchRectangle.Top + m_nItemHeight / 2); + if (nCounter != 0) { + for (int i = 0; i <= nLevel; i++) { + g.DrawLine(m_pen, 9 + i * 10, items.SwitchRectangle.Top - m_nItemHeight / 2, 9 + i * 10, items.SwitchRectangle.Top + m_nItemHeight / 2 - 1); + } + } + this.OnDrawItemText(dt, items, rect); + this.OnDrawItemIcon(dt, items, rect); + } + /// + /// Occurs when drawing the tree node expand and close switches + /// + /// Drawing tool + /// The current collection that needs to be drawn + protected virtual void OnDrawSwitch(DrawingTools dt, STNodeTreeCollection items) { + Graphics g = dt.Graphics; + if (items.Count != 0) { + m_pen.Color = this._SwitchColor; + m_brush.Color = m_pen.Color; + int nT = items.SwitchRectangle.Y + m_nItemHeight / 2 - 4; + g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8); + g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4); + if (items.IsOpen) return; + g.DrawLine(m_pen, items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7); + //if (items.IsOpen) { + // //g.FillPolygon(m_brush, new Point[]{ + // // new Point(items.DotRectangle.Left + 0, items.DotRectangle.Top + m_nItemHeight / 2 - 2), + // // new Point(items.DotRectangle.Left + 9, items.DotRectangle.Top + m_nItemHeight / 2 - 2), + // // new Point(items.DotRectangle.Left + 4, items.DotRectangle.Top + m_nItemHeight / 2 + 3) + // //}); + // g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8); + // g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4); + //} else { + // //g.FillPolygon(m_brush, new Point[]{ + // // new Point(items.DotRectangle.Left + 2, items.DotRectangle.Top + m_nItemHeight / 2 - 5), + // // new Point(items.DotRectangle.Left + 2, items.DotRectangle.Top + m_nItemHeight / 2 + 5), + // // new Point(items.DotRectangle.Left + 7, items.DotRectangle.Top + m_nItemHeight / 2) + // //}); + // g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8); + // g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4); + // g.DrawLine(m_pen, items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7); + //} + } + } + /// + /// Occurs when the text of the tree node is drawn + /// + /// Drawing tool + /// The current collection that needs to be drawn + /// The rectangular area where the text field is located + protected virtual void OnDrawItemText(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) { + Graphics g = dt.Graphics; + rect.Width -= 20; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + if (!string.IsNullOrEmpty(m_str_search)) { + int nIndex = items.NameLower.IndexOf(m_str_search); + if (nIndex != -1) { + CharacterRange[] chrs = { new CharacterRange(nIndex, m_str_search.Length) };//global + m_sf.SetMeasurableCharacterRanges(chrs); + Region[] regions = g.MeasureCharacterRanges(items.Name, this.Font, rect, m_sf); + g.SetClip(regions[0], System.Drawing.Drawing2D.CombineMode.Intersect); + m_brush.Color = this._HightLightTextColor; + g.DrawString(items.Name, this.Font, m_brush, rect, m_sf); + g.ResetClip(); + g.SetClip(regions[0], System.Drawing.Drawing2D.CombineMode.Exclude); + m_brush.Color = items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 1 / 2, this.ForeColor) : this.ForeColor; + g.DrawString(items.Name, this.Font, m_brush, rect, m_sf); + g.ResetClip(); + return; + } + } + m_brush.Color = items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 2 / 3, this.ForeColor) : this.ForeColor; + g.DrawString(items.Name, this.Font, m_brush, rect, m_sf); + } + /// + /// Occurs when the tree node icon is drawn + /// + /// Drawing tool + /// The current collection that needs to be drawn + /// The rectangular area where the text field is located + protected virtual void OnDrawItemIcon(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) { + Graphics g = dt.Graphics; + if (items.STNodeType != null) { + m_pen.Color = this._AutoColor ? items.STNodeTypeColor : Color.DarkCyan; + m_brush.Color = Color.LightGray; + g.DrawRectangle(m_pen, rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10); + g.FillRectangle(m_brush, rect.Left - 17, rect.Top + m_nItemHeight / 2 - 2, 5, 5); + g.FillRectangle(m_brush, rect.Left - 6, rect.Top + m_nItemHeight / 2 - 2, 5, 5); + if (m_item_hover == items && m_bHoverInfo) { + m_brush.Color = this.BackColor; + g.FillRectangle(m_brush, items.InfoRectangle); + } + m_pen.Color = this._AutoColor ? items.STNodeTypeColor : this._InfoButtonColor; + m_pen.Width = 2; + g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 3, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 3); + g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 6, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 6); + g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 11, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 11); + g.DrawLine(m_pen, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 7, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 10); + m_pen.Width = 1; + g.DrawRectangle(m_pen, items.InfoRectangle.X, items.InfoRectangle.Y, items.InfoRectangle.Width - 1, items.InfoRectangle.Height - 1); + } else { + if (items.IsLibraryRoot) { + Rectangle rect_box = new Rectangle(rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10); + g.DrawRectangle(Pens.Gray, rect_box); + g.DrawLine(Pens.Cyan, rect_box.X - 2, rect_box.Top, rect_box.X + 2, rect_box.Top); + g.DrawLine(Pens.Cyan, rect_box.X, rect_box.Y - 2, rect_box.X, rect_box.Y + 2); + g.DrawLine(Pens.Cyan, rect_box.Right - 2, rect_box.Bottom, rect_box.Right + 2, rect_box.Bottom); + g.DrawLine(Pens.Cyan, rect_box.Right, rect_box.Bottom - 2, rect_box.Right, rect_box.Bottom + 2); + } else { + g.DrawRectangle(Pens.Goldenrod, new Rectangle(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 6, 8, 3)); + g.DrawRectangle(Pens.Goldenrod, new Rectangle(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 3, 13, 9)); + } + if (!this._ShowFolderCount) return; + m_sf.Alignment = StringAlignment.Far; + m_brush.Color = this._FolderCountColor; + rect.X -= 4; + g.DrawString("[" + items.STNodeCount.ToString() + "]", this.Font, m_brush, rect, m_sf); + m_sf.Alignment = StringAlignment.Near; + } + } + + #endregion + + #region public method ========== + /// + /// Retrieve the STNode in the control + /// + /// Text to retrieve + public void Search(string strText) { + if (strText == null) return; + if (strText.Trim() == string.Empty) return; + m_tbx.Text = strText.Trim (); + } + /// + /// Add a STNode type to the control + /// + /// STNode type + /// Whether the addition is successful + public bool AddNode(Type stNodeType) { return this.AddSTNode(stNodeType, m_items_source, null, true); } + /// + /// Add the STNode type to the control from the file + /// + /// Specify file path + /// Add success number + public int LoadAssembly(string strFile) { + strFile = System.IO.Path.GetFullPath(strFile); + var items = this.AddAssemblyPrivate(strFile); + if (items.STNodeCount == 0) return 0; + items.IsLibraryRoot = true; + m_items_source[items.Name] = items; + return items.STNodeCount; + } + /// + /// Clear all STNode types in the control + /// + public void Clear() { + m_items_source.Clear(); + m_items_draw.Clear(); + m_dic_all_type.Clear(); + this.Invalidate(); + } + /// + /// Remove an STNode type from the control + /// + /// STNode type + /// Whether the removal is successful + public bool RemoveNode(Type stNodeType) { + if (!m_dic_all_type.ContainsKey(stNodeType)) return false; + string strPath = m_dic_all_type[stNodeType]; + STNodeTreeCollection items = m_items_source; + if (!string.IsNullOrEmpty(strPath)) { + string[] strKeys = strPath.Split(m_chr_splitter); + for (int i = 0; i < strKeys.Length; i++) { + items = items[strKeys[i]]; + if (items == null) return false; + } + } + try { + STNode node = (STNode)Activator.CreateInstance(stNodeType); + if (items[node.Title] == null) return false; + items.Remove(node.Title, true); + m_dic_all_type.Remove(stNodeType); + } catch { return false; } + this.Invalidate(); + return true; + } + + #endregion + //================================================================================================= + /// + /// A collection of each item in the STNodeTreeView control + /// + protected class STNodeTreeCollection : IEnumerable + { + private string _Name; + /// + /// Get the current tree node display name + /// + public string Name { + get { + return _Name; + } + } + /// + /// Get the lowercase string of the display name of the current tree node + /// + public string NameLower { get; private set; } + /// + /// Get the STNode type corresponding to the current tree node + /// + public Type STNodeType { get; internal set; } + /// + /// Get the parent tree node of the current tree node + /// + public STNodeTreeCollection Parent { get; internal set; } + + /// + /// Get the number of STNode types owned by the current tree node + /// + public int STNodeCount { get; internal set; } + /// + /// Get the corresponding path of the STNode type corresponding to the current tree node in the tree control + /// + public string Path { get; internal set; } + /// + /// Get the current or set whether the tree node is open + /// + public bool IsOpen { get; set; } + /// + /// Get whether the current tree node is the root node of the loaded module + /// + public bool IsLibraryRoot { get; internal set; } + /// + /// Get the display area of ​​the current tree node in the control + /// + public Rectangle DisplayRectangle { get; internal set; } + /// + /// Get the switch button area of ​​the current tree node in the control + /// + public Rectangle SwitchRectangle { get; internal set; } + /// + /// Get the information button area of ​​the current tree node in the control + /// + public Rectangle InfoRectangle { get; internal set; } + /// + /// Get the title color of the STNode type corresponding to the current tree node + /// + public Color STNodeTypeColor { get; internal set; } + /// + /// Get the number of child nodes contained in the current tree node + /// + public int Count { get { return m_dic.Count; } } + /// + /// Gets or sets the collection with the specified name + /// + /// specify name + /// Collection + public STNodeTreeCollection this[string strKey] { + get { + if (string.IsNullOrEmpty(strKey)) return null; + if (m_dic.ContainsKey(strKey)) return m_dic[strKey]; + return null; + } + set { + if (string.IsNullOrEmpty(strKey)) return; + if (value == null) return; + if (m_dic.ContainsKey(strKey)) { + m_dic[strKey] = value; + } else { + m_dic.Add(strKey, value); + } + value.Parent = this; + } + } + + private SortedDictionary m_dic = new SortedDictionary(); + /// + /// Construct a tree node collection + /// + /// Display name of the current tree node in the control + public STNodeTreeCollection(string strName) { + if (strName == null || strName.Trim() == string.Empty) + throw new ArgumentNullException("Display name cannot be null"); + this._Name = strName.Trim(); + this.NameLower = this._Name.ToLower(); + } + /// + /// Add a child node to the current tree node + /// + /// node display name + /// Added set of child nodes + public STNodeTreeCollection Add(string strName) { + if (!m_dic.ContainsKey(strName)) + m_dic.Add(strName, new STNodeTreeCollection(strName) { Parent = this }); + return m_dic[strName]; + } + /// + /// Delete a subcollection from the current tree node + /// + /// Subcollection name + /// Whether to automatically clear useless nodes recursively upwards + /// Whether the deletion is successful + public bool Remove(string strName, bool isAutoDelFolder) { + if (!m_dic.ContainsKey(strName)) return false; + bool b = m_dic.Remove(strName); + var temp = this; + while (temp != null) { + temp.STNodeCount--; + temp = temp.Parent; + } + if (isAutoDelFolder && m_dic.Count == 0 && this.Parent != null) + return b && this.Parent.Remove(this.Name, isAutoDelFolder); + return b; + } + /// + /// Clear all child nodes in the current tree node + /// + public void Clear() { this.Clear(this); } + + private void Clear(STNodeTreeCollection items) { + foreach (STNodeTreeCollection v in items) v.Clear(v); + m_dic.Clear(); + } + /// + /// Get all the name arrays in the current tree node + /// + /// + public string[] GetKeys() { return m_dic.Keys.ToArray(); } + /// + /// Copy all data in the current tree node set + /// + /// Copy copy + public STNodeTreeCollection Copy() { + STNodeTreeCollection items = new STNodeTreeCollection("COPY"); + this.Copy(this, items); + return items; + } + + private void Copy(STNodeTreeCollection items_src, STNodeTreeCollection items_dst) { + foreach (STNodeTreeCollection v in items_src) { + this.Copy(v, items_dst.Add(v.Name)); + } + items_dst.Path = items_src.Path; + items_dst.STNodeType = items_src.STNodeType; + items_dst.IsLibraryRoot = items_src.IsLibraryRoot; + items_dst.STNodeCount = items_src.STNodeCount; + items_dst.STNodeTypeColor = items_src.STNodeTypeColor; + } + /// + /// Returns an Array of System.Collections.IEnumerator + /// + /// + public IEnumerator GetEnumerator() { + foreach (var v in m_dic.Values) yield return v; + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } + } +} \ No newline at end of file