diff --git a/.gitignore b/.gitignore index 4f95e2b..e37e314 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,364 @@ -.DS_Store -/Bin -bin/ -obj/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +.idea/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/README.md b/README.md index 1131111..6be7d46 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,399 @@ -## STNodeEditor +# Version 3.0 -`STNodeEditor` 是一个很不错的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件 可以非常方便的完成节点之间数据的交互及通知 大量的重载函数供开发者使用具有很高的自由性 +现在: 2022-08-30 -编译环境: `VS 2010 (.Net 3.5)` +`3.0`版本进入排期开发,开发进度将同步更新。具体说明请查看 [V3_CN.md](./V3_CN.md) -![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/node.png) -此控件采用 [`MIT`](https://opensource.org/licenses/mit-license.php) 开源协议开源 使开发者能够拥有更大的自由度更少的限制 +now: 2022-08-30 -`STNodeEditor` 拥有非常方便的UI自定义能力 提供的 `STNodeControl` 基类 可以让开发者能够像自定义 `WinForm` 控件一样去定义 节点需要的控件 +The version `3.0` has been start coding,The development progress is updated synchronously. more info refer [V3_EN.md](./V3_EN.md) -![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/formImage.png) +2023-04-20 -## 简要说明 -* 画布 - * 移动 `鼠标中键` 拖动 Mac用户可二指拖动 `触控板` - * 缩放 按下 `Control` + `鼠标滚轮` - * 画布中的节点内容以及连线关系可通过 `STNodeEditor.Load/SaveCanvas()` 加载或者保存 -* 删除连线 - * 悬停到连线上 `鼠标右键` -* 移动节点 - * `鼠标左键` 拖动 `节点标题` - * 之所以是拖动标题而不是节点任意位置 是因为作者的设计思路是将节点视为一个 `窗体` 窗体的客户区域留给开发者自定义 -* STNode - * 如同 `System.Windows.Forms.TreeView` 一样 所有的节点都保存在 `STNodeEditor.Nodes` 中 其数据类型为 `STNode` - * `STNode` 为抽象类被 `abstract` 修饰 需要开发者自己继承向节点中添加选项 - * `STNode` 有三个重要属性 `InputOptions` `OutputOptions` `Controls` +老铁们,最近问更新情况的人比较多。确实摆烂了一段时间,什么也不想做。不过现在陆陆续续回到状态了。不过在继续更新之前手里还有一个[STJson](https://github.com/DebugST/STJson)。也正是当前项目需要的。`STJson`项目也摆烂很久了。。不过这两天应该会完工了,提供了全套的`Json`解析操作包括`JsonPath`的支持。。如果有经常使用`Json`的小伙伴可以关注一下。现在已经进入最后的调试和文档教程编写阶段。。等完工后回到`STNodeEditor`的开发,并且`STNodeEditor`的数据保存也将提供`Json`格式。 -## 示例 -```cs -public class DemoNode : STNode +# STNodeEditor +[![VS2010](https://img.shields.io/badge/Visual%20Studio-2010-blueviolet)](https://visualstudio.microsoft.com/zh-hans/vs/) [![.NET35](https://img.shields.io/badge/DotNet-3.5-blue)](https://www.microsoft.com/zh-cn/download/details.aspx?id=25150) [![NuGet](https://img.shields.io/badge/NuGet-5.9-blue)](https://www.nuget.org/packages/ST.Library.UI/) [![license](https://img.shields.io/badge/License-MIT-green)](https://github.com/DebugST/STNodeEditor/blob/main/LICENSE) + +STNodeEditor 是一个轻量且功能强大的节点编辑器 纯`GDI`实现无任何依赖库仅仅`100+Kb` 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的虚函数可供开发者重写具有很高的自由性 + +Environment: VS2010(.NET 3.5) + +![STNodeEditor](https://debugst.github.io/STNodeEditor/images/page_top.png) +![STNodeEditor](https://debugst.github.io/STNodeEditor/images/node_scan.png) + +项目主页 (Project home): [DebugST.github.io/STNodeEditor](https://DebugST.github.io/STNodeEditor) (简体中文, English) + +教程文档: [DebugST.github.io/STNodeEditor/doc_cn.html](https://DebugST.github.io/STNodeEditor/doc_cn.html) + +Tutorials and API: [DebugST.github.io/STNodeEditor/doc_en.html](https://DebugST.github.io/STNodeEditor/doc_en.html) + +Mail: (2212233137@qq.com) + +NuGet: [https://www.nuget.org/packages/ST.Library.UI/](https://www.nuget.org/packages/ST.Library.UI/) + +``` +PM> Install-Package ST.Library.UI -Version 2.0.0 +``` +--- +# 简介 + +那是一个冬季 在研究无线电安全的作者接触到了[GNURadio](https://www.gnuradio.org/) 那是作者第一次接触到节点编辑器 + +-> What? Excuse me... What"s this?.. 这是什么鬼东西?... + +那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了[Blender](https://www.blender.org/)那是作者第二次接触到节点编辑器 + +-> Wo...原来这东西可以这么玩...真方便 + +于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法 + +那是一个夏季 不知道为什么 作者又玩起了[Davinci](http://www.blackmagicdesign.com/cn/products/davinciresolve/)那是作者第三次接触到节点编辑器 这一次的接触让作者对节点编辑器的好感倍增 作者瞬间觉得 只要是可以模块化流程化的功能 万物皆可节点化 + +--- + +# 像流程图一样使用你的功能 + +你是否有设想过流程图不再是流程图 而是直接可以执行的? + +在一些开发过程中我们可能会为整个程序设计一个流程图 上面包含了我们存在的功能模块以及执行流程 然后由开发者逐一实现 + +但是这样会带来一些问题 程序的执行流程可能会被硬编码到程序中去 如果突然有一天可能需要改变执行顺序或者添加删除一个执行模块 可能需要开发者对代码重新编辑然后编译 而且各个功能模块之间的调用也需要开发者进行编码调度 增加开发成本 等一系列的问题 + +而 `STNodeEditor` 就是为此诞生 + +--- + +`STNodeEditor` 包含3部分 `TreeView` `PropertyGrid` `NodeEditor` 这三部分组成了一套完整的可使用框架 + +* TreeView + * 可以把执行功能编码到一个节点中 而 `TreeView` 则负责展示以及检索节点 在 `TreeView` 中的节点可直接拖拽添加到 `NodeEditor` 中 +* PropertyGrid + * 类似与 `WinForm` 开发使用的属性窗口 作为一个节点 它也是可以有属性的 而作者在编辑器进行设计的过程中也把一个节点视作一个 `Form` 让开发者几乎没有什么学习成本直接上手一个节点的开发 +*NodeEditor + *`NodeEditor` 是用户组合自己执行流程的地方 使得功能模块执行流程可视化 +--- +# 如何使用它? + +STNodeEditor的使用非常简单 你几乎可以没有任何学习成本的去使用的 当然最重要的一点就是 你需要知道如何去创建一个节点 + +你可以像创建一个Form一样去创建一个Node + +``` cs +using ST.Library.UI.NodeEditor; + +public class MyNode : STNode { - protected override void OnCreate() { - //在创建节点时候向其添加需要的选项 + public MyNode() { //等同于 [override void Oncreate(){}] + this.Title = "MyNode"; + this.TitleColor = Color.FromArgb(200, Color.Goldenrod); + this.AutoSize = false; + this.Size = new Size(100, 100); + + var ctrl = new STNodeControl(); + ctrl.Text = "Button"; + ctrl.Location = new Point(10, 10); + this.Controls.Add(ctrl); + ctrl.MouseClick += new MouseEventHandler(ctrl_MouseClick); + } + + void ctrl_MouseClick(object sender, MouseEventArgs e) { + MessageBox.Show("MouseClick"); + } +} +//添加到编辑器中 +stNodeEditor.Nodes.Add(new MyNode()); +``` +MyNode.png + +可以看到它的使用方式和 `Form` 确实很像 其实目前还暂时没有提供所见即所得的UI设计器 而且一个 `STNode` 它同样有它的控件集合且数据类型为 `STNodeControl` + +`STNodeControl` 作为 `STNode` 控件的基类 它拥有着和 `System.Windows.Forms.Control` 许多同名的属性和事件 一切的初衷都只为与 `WinForm` 靠近 + + +**注意:在目前的版本中(2.0) STNodeEditor仅仅提供了STNodeControl基类 并未提供任何一个可用控件 当然在附随的Demo工程中包含了部分示例演示如何自定义一个控件 由于这属于自定义控件的范畴 所以演示并未太多 若需了解关于自定义控件如何开发可参考作者:[自定义控件开发](http://st233.com/blog.php?group=1) 系列文章 当然在后续的版本中 作者将提供部分常用控件 虽说作者想把使用方式往WinForm上靠 单仅仅是把它当作WinForm使用并不是作者的初衷** + +上面的演示仅仅是为了让大家感到亲切感 毕竟 `WinForm` 可能是大家熟悉的一个东西 但是如果仅仅是把它当作 `WinForm` 使用毫无意义 对于一个节点来说 最重要的属性当然是数据的输入和输出 + +``` cs +public class MyNode : STNode +{ + protected override void OnCreate() {//等同 [public MyNode(){}] base.OnCreate(); - this.InputOptions.Add(new STNodeOption("Input", typeof(string), false)); - this.InputOptions.Add(new STNodeOption("SingleNode", typeof(System.Drawing.Image), true)); - this.InputOptions.Add(new STNodeOption("SingleNode", typeof(object), true)); + this.Title = "TestNode"; + //可以得到添加的索引位置 + int nIndex = this.InputOptions.Add(new STNodeOption("IN_1", typeof(string), false)); + //可以得到添加的 STNodeOption + STNodeOption op = this.InputOptions.Add("IN_2", typeof(int), true); + this.OutputOptions.Add("OUT", typeof(string), false); + } + //当所有者发生改变(即:在NodeEditor中被添加或移除) + //应当像容器提交自己拥有数据类型的连接点 所期望显示的颜色 + //颜色主要用于区分不同的数据类型 + protected override void OnOwnerChanged() { + base.OnOwnerChanged(); + if (this.Owner == null) return; + this.Owner.SetTypeColor(typeof(string), Color.Yellow); + //当前容器中已有的颜色会被替换 + this.Owner.SetTypeColor(typeof(int), Color.DodgerBlue, true); + //下面的代码将忽略容器中已有的颜色 + //this.SetOptionDotColor(op, Color.Red); //无需在OnOwnerChanged()中设置 + } +} +``` + +MyNode.png - this.OutputOptions.Add(new STNodeOption("output", typeof(string), false)); - this.OutputOptions.Add(new STNodeOption("Single", typeof(System.Drawing.Icon), true)); - this.OutputOptions.Add(new STNodeOption("Single", typeof(object), true)); +通过上面的案例你可以看到 `STNode` 有两个重要的属性 `InputOptions` 和 `OutputOptions` 其数据类型为 `STNodeOption` 而 `STNodeOption` 有两种连接模式 `single-connection` 和 `multi-connection` - this.Title = "Demo_Node"; +* single-connection + * 单连接模式 在单连接模式下一个连接点同时 只能被一个 同数据类型点的连接 +* multi-connection + * 多连接模式 在多连接模式下一个连接点同时 可以被多个 同数据类型点连接 + +``` cs +public class MyNode : STNode { + protected override void OnCreate() { + base.OnCreate(); + this.Title = "MyNode"; + this.TitleColor = Color.FromArgb(200, Color.Goldenrod); + //multi-connection + this.InputOptions.Add("Single", typeof(string), true); + //single-connection + this.OutputOptions.Add("Multi", typeof(string), false); } } -//stNodeEditor1.SetTypeColor(type, color);此方法会自动替换已存在值 -stNodeEditor1.TypeColor.Add(typeof(string), Color.Yellow); -stNodeEditor1.TypeColor.Add(typeof(Image), Color.Red); -stNodeEditor1.Nodes.Add(new DemoNode()); ``` -上述代码的 `DemoNode` 被添加到节点编辑器中的效果为 +MyNode.png -![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/node_demo.png) +--- -由此可见 开发者要自定义一个节点相当便捷 当然上述代码并没有包含事件处理 上述代码仅仅是在演示 `STNodeOption` 的效果 下列代码则是一个包含了计算两个整数和的功能 -```cs -public class NodeNumberAdd : STNode -{ - private STNodeOption m_in_num1; - private STNodeOption m_in_num2; - private STNodeOption m_out_num; - private int m_nNum1, m_nNum2; +# 如何进行数据交互? + +在上面的案例中仅仅是做了一个可以被连接的选项点 并不包含任何的功能 + +* STNodeOption可以通过绑定DataTransfer事件获取到传入该选项的所有数据 +* STNodeOption可以通过TransferData(object obj)向该选项上所有连接的选项进行数据投递 +下面通过一个案例进行演示 创建两个节点 一个节点用于每秒输出一次当前系统事件 另一个节点用于接收一个事件并显示 + +``` cs +public class ClockNode : STNode +{ + private Thread m_thread; + private STNodeOption m_op_out_time; + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "ClockNode"; + m_op_out_time = this.OutputOptions.Add("Time", typeof(DateTime), false); + } + //当被添加或者移除 + protected override void OnOwnerChanged() { + base.OnOwnerChanged(); + if (this.Owner == null) { //如果是被移除 停止线程 + if (m_thread != null) m_thread.Abort(); + return; + } + this.Owner.SetTypeColor(typeof(DateTime), Color.DarkCyan); + m_thread = new Thread(() => { + while (true) { + Thread.Sleep(1000); + //STNodeOption.TransferData(object)会自动设置STNodeOption.Data + //然后自动向所有连接的选项进行数据传递 + m_op_out_time.TransferData(DateTime.Now); + //如果你需要一些耗时操作STNode同样提供了Begin/Invoke()操作 + //this.BeginInvoke(new MethodInvoker(() => { + // m_op_out_time.TransferData(DateTime.Now); + //})); + } + }) { IsBackground = true }; + m_thread.Start(); + } +} +``` +当然上面可以直线将时间显示出来 不过这里为了演示数据的传递 所以还需要一个接收节点 +``` cs +public class ShowClockNode : STNode { + private STNodeOption m_op_time_in; protected override void OnCreate() { base.OnCreate(); - this.Title = "NumberAdd"; - m_in_num1 = new STNodeOption("num1", typeof(int), true);//只能有一个连线 - m_in_num2 = new STNodeOption("num2", typeof(int), true);//只能有一个连线 - m_out_num = new STNodeOption("result", typeof(int), false);//可以多个连线 - this.InputOptions.Add(m_in_num1); - this.InputOptions.Add(m_in_num2); - this.OutputOptions.Add(m_out_num); - m_in_num1.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer); - m_in_num2.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer); - } - //当有数据传入时 - void m_in_num_DataTransfer(object sender, STNodeOptionEventArgs e) { - //判断连线是否是连接状态(建立连线 断开连线 都会触发该事件) - if (e.Status == ConnectionStatus.Connected) { - if (sender == m_in_num1) - m_nNum1 = (int)e.TargetOption.Data;//TargetOption为触发此事件的Option - else - m_nNum2 = (int)e.TargetOption.Data; + this.Title = "ShowTime"; + //采用 "single-connection" 模式 + m_op_time_in = this.InputOptions.Add("--", typeof(DateTime), true); + //当有数据时会自动触发此事件 + m_op_time_in.DataTransfer += new STNodeOptionEventHandler(op_DataTransfer); + } + + void op_DataTransfer(object sender, STNodeOptionEventArgs e) { + //当连接的建立与断开都会触发此事件 所以需要判断连接状态 + if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) { + //当 STNode.AutoSize=true 并不建议使用STNode.SetOptionText + //因为当文本发生改变时候会重新计算布局 正确的做法是自定义一个如Lable控件 + //作为时间的显示 当然这里为了演示方式采用此方案 + this.SetOptionText(m_op_time_in, "--"); } else { - if (sender == m_in_num1) m_nNum1 = 0; else m_nNum2 = 0; + this.SetOptionText(m_op_time_in, ((DateTime)e.TargetOption.Data).ToString()); } - //向输出选项上的所有连线传输数据 输出选项上的所有连线都会触发 DataTransfer 事件 - m_out_num.TransferData(m_nNum1 + m_nNum2); //m_out_num.Data 将被自动设置 } +} +``` +TimeNode.gif - protected override void OnOwnerChanged() { - //通常刚被添加到节点编辑器时触发 如是以插件方式提供的节点 应当向编辑器提交数据类型颜色 - base.OnOwnerChanged(); - if (this.Owner == null) return; //或者通过m_in_num1.DotColor = Color.Red;进行设置 - this.Owner.SetTypeColor(typeof(int),Color.Red); +可以看到当连接被建立时 `ShowTime` 节点每秒都在刷新 下面是一个更加复杂一点的案例 但是这里并没有给出代码请参考附随工程的 `Demo` + +ImageNode.png + +点击 `Open Image` 按钮可打开并显示一张图片在 `ImageShowNode` 中并将图片作为输出数据 `ImageChanel` 则负责接收一张图像并处理输出图像的RGB图像及原图 `ImageSize` 则负责接收并显示一张图像的尺寸信息 + +对于上面的节点在开发期间它们并不知道会被什么样的节点连接 也并不知道会被连接到什么节点上 开发者仅仅是完成了自己的功能处理接收到的数据并将结果打包给 `STNodeOption` 无需关系最终会被谁把结果拿走并处理 使得节点之间与节点之间的耦合关系大大降低 唯一将它们联系在一起的是一个 `Image` 数据类型 最终执行的逻辑交给用户自己拖拽节点组合他们自己想要的流程 使得功能的执行流程变得可视化 这也是作者的初衷 + +**关于更多的教程和文档请参考:[https://debugst.github.io/STNodeEditor/doc_cn.html](https://debugst.github.io/STNodeEditor/doc_cn.html) 在下载的调用库压缩包里面同样包含离线版文档** + +--- + +# 关于下个版本 + +其实目前这个版本还有很多需要完善的代码 如上面提到的提供一些基础控件 而且目前提供的东西还很原始 一些应用场景目前需要开发者自己写代码完成 + +First.png + +上图为作者的最初构思以及第一个 `Demo` 演示版本 在上图中可以看到有 `启动` 按钮 某些应用场景下可能需要用户点击执行按钮以后才开始执行用户所部署的逻辑 而之前上面的案例数据交互都是更具用户的布线实时的 当然在目前的版本中想实现也是可以的 只是需要开发者自己写部分代码 由于这部分的代码作者暂时还没有构思好很多细节处理 所以还有下一个版本的话很多功能都将出现 + +上图的构想是 开发者无需关系架构执行逻辑什么的 而开发者只需要关系功能点本省只需要开发出包含 `STNode` 的 `DLL` 文件 而程序启动 `TreeView` 会自动加载目录下的 `DLL` 文件并装载 `STNode` 到 `TreeView` 中 然后让用户拖拽执行 对于上一段话中作者提到的需要通过 启动 按钮执行如何在当前版本的实现 作者这里给出一些思路 + +``` cs +//首先定义一个基类 包含Start和Stop方法 +public abstract class BaseNode : STNode +{ + public abstract void Start(); + public abstract void Stop(); +} +//=================================================================== +//然后再基于基类在定义3个类型 +//InputNode 将作为开始节点 作为数据执行的入口节点 类似与Main函数一样 +public abstract class InputNode : BaseNode { } +//OutputNode 将作为最终数据的处理节点 如文件保存 数据库保存等 +public abstract class OutputNode : BaseNode { } +//更具自己需求定义一些其他执行功能的节点 +public abstract class ExecNode : BaseNode { } +//=================================================================== +//创建一个 TestInputNode 提供一个字符串输入 并作为开始节点 +public class TestInputNode : InputNode +{ + //使用"STNodeProperty"特性则此属性会在"STNodePropertyGrid"中显示 + [STNodeProperty("希望显示的属性名字", "属性秒速")] + public string TestText { get; set; } + + private STNodeOption m_op_out; + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "StringInput"; + m_op_out = this.OutputOptions.Add("OutputString", typeof(string), false); + } + + public override void Start() { + //当执行开始的时候才向连接的选项进行数据的传递 + m_op_out.TransferData(this.TestText); + this.LockOption = true;//开始后锁定选项 + } + + public override void Stop() { + this.LockOption = false;//结束后解锁选项 + } +} +//=================================================================== +//创建一个 TextFileOutputNode 用于文本文件保存收到的字符串 +public class TextFileOutputNode : OutputNode +{ + [STNodeProperty("属性显示名称", "属性描述")] + public string FileName { get; set; } + + private StreamWriter m_writer; + + protected override void OnCreate() { + base.OnCreate(); + this.InputOptions.Add("Text", typeof(string), false) + .DataTransfer += new STNodeOptionEventHandler(op_DataTransfer); + } + + void op_DataTransfer(object sender, STNodeOptionEventArgs e) { + if (e.Status != ConnectionStatus.Connected) return; + if (e.TargetOption.Data == null) return; + if (m_writer == null) return; + //当收到一个数据时候 写入文本 + lock (m_writer) m_writer.WriteLine(e.TargetOption.Data.ToString()); + } + + public override void Start() { + //开始的时候初始化文件 + m_writer = new StreamWriter(this.FileName, false, Encoding.UTF8); + this.LockOption = true; + } + + public override void Stop() { + this.LockOption = false; + if (m_writer == null) return; + m_writer.Close(); + m_writer = null; } } ``` -效果为 -![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/node_add.png) +上面的代码演示了一个 `输入` 和 `输出` 类型的节点 至于其他需求自行举一反三 当用户点下 `启动` 按钮时候 -更多细节请参考文档 +``` cs +public void OnClickStart() { + List lst_input = new List(); + List lst_output = new List(); + List lst_other = new List(); + foreach (var v in stNodeEditor.Nodes) { + if ((v is BaseNode)) continue; + if (v is InputNode) { + lst_input.Add((InputNode)v); + } else if (v is OutputNode) { + lst_output.Add((OutputNode)v); + } else { + lst_other.Add((BaseNode)v); + } + } + //在真正的开始之前 应当处理一些事情 + if (lst_output.Count == 0) + throw new Exception("没有找到 [OutputNode] 类型的节点 请添加."); + if (lst_input.Count == 0) + throw new Exception("没有找到 [InputNode] 类型的节点 请添加."); + foreach (var v in lst_other) v.Start(); + foreach (var v in lst_output) v.Start(); + //最起码 InputNode 类型的节点至少得又一个吧 不然怎么开始. + //而且 InputNode 类型的节点应当是最后启动 + foreach (var v in lst_input) v.Start(); + stNodePropertyGrid1.ReadOnlyModel = true;//不要忘记设置属性窗口只读 +} +``` + +如果你希望只能有一个 InputNode 类型的节点被添加 + +``` cs +stNodeEditor.NodeAdded += new STNodeEditorEventHandler(stNodeEditor_NodeAdded); +void stNodeEditor_NodeAdded(object sender, STNodeEditorEventArgs e) { + int nCounter = 0; + foreach (var v in stNodeEditor.Nodes) { + if (v is InputNode) nCounter++; + } + if (nCounter > 1) { + System.Windows.Forms.MessageBox.Show("只能有一个 InputNode 被添加"); + stNodeEditor.Nodes.Remove(e.Node); + } +} +``` -项目主页: [DebugST.github.io/DotNet_WinForm_NodeEditor](https://DebugST.github.io/DotNet_WinForm_NodeEditor) +当然 这个需求估计很少有吧 -开发文档: [DebugST.github.io/DotNet_WinForm_NodeEditor/doc.html](https://DebugST.github.io/DotNet_WinForm_NodeEditor/doc.html) +当然这里就并没有给出上述代码片段的执行效果了 因为上面仅仅是提供思路 让读者可以举一反三 而且上面的代码均没有任何的异常处理 要真正做好其实还有很多细节需要处理很多代码需要写 所以暂定目前版本不提供这样的功能 -## 关于作者 -* Github: [DebugST](https://DebugST.github.io) +# 关于作者 +* Github: [DebugST](https://github.com/DebugST/) * Blog: [Crystal_lz](http://st233.com) * Mail: (2212233137@qq.com) diff --git a/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs b/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs index 09b61f2..04c769c 100644 --- a/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs +++ b/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs @@ -7,6 +7,7 @@ using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; +using SkiaSharp; namespace ST.Library.UI.NodeEditor { @@ -27,8 +28,6 @@ internal class FrmNodePreviewPanel : Form 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")] @@ -91,11 +90,7 @@ protected override void OnLoad(EventArgs e) { 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); - } + FrmNodePreviewPanel.SetWindowRgn(this.Handle, IntPtr.Zero, false); this.MouseLeave += Event_MouseLeave; m_editor.MouseLeave += Event_MouseLeave; @@ -122,23 +117,6 @@ void Event_MouseLeave(object sender, EventArgs e) { 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); } } } diff --git a/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs b/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs index d5574e4..98b81ca 100644 --- a/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs +++ b/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs @@ -6,6 +6,7 @@ using System.Windows.Forms; using System.Drawing; using ST.Library.UI.NodeEditor; +using SkiaSharp; namespace ST.Library.UI { @@ -13,8 +14,6 @@ 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) { @@ -29,8 +28,6 @@ public FrmSTNodePropertyInput(STNodePropertyDescriptor 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) { @@ -57,36 +54,6 @@ protected override void OnLoad(EventArgs e) { 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) { diff --git a/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs b/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs index 3adf24e..94b85ef 100644 --- a/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs +++ b/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs @@ -5,6 +5,7 @@ using System.Windows.Forms; using System.Drawing; +using SkiaSharp; namespace ST.Library.UI.NodeEditor { @@ -14,9 +15,6 @@ internal class FrmSTNodePropertySelect : Form 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; @@ -33,11 +31,6 @@ public FrmSTNodePropertySelect(STNodePropertyDescriptor descriptor) { 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(); @@ -65,29 +58,6 @@ protected override void OnLoad(EventArgs e) { 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) { diff --git a/ST.Library.UI/NodeEditor/STNode.cs b/ST.Library.UI/NodeEditor/STNode.cs index 07e422a..c80fd2f 100644 --- a/ST.Library.UI/NodeEditor/STNode.cs +++ b/ST.Library.UI/NodeEditor/STNode.cs @@ -6,6 +6,7 @@ using System.Drawing; using System.Windows.Forms; using System.Collections; +using SkiaSharp; /* MIT License @@ -499,20 +500,18 @@ public STNode() { 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 (this._AutoSize && bBuildNode) { + Size sz = this.GetDefaultNodeSize(); + 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(); + } if (bRedraw) this._Owner.Invalidate(); } @@ -581,11 +580,12 @@ protected virtual void OnCreate() { } /// /// 绘制工具 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); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + using (var bg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._BackColor), Style = SKPaintStyle.Fill, IsAntialias = true }) { + canvas.DrawRect(this._Left, this._Top + this._TitleHeight, this._Width, this.Height - this._TitleHeight, bg); + } + }); } this.OnDrawTitle(dt); this.OnDrawBody(dt); @@ -597,43 +597,38 @@ protected internal virtual void OnDrawNode(DrawingTools dt) { 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); - } + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + if (this._TitleColor.A != 0) { + using (var title = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._TitleColor), Style = SKPaintStyle.Fill, IsAntialias = true }) { + canvas.DrawRect(this.TitleRectangle.Left, this.TitleRectangle.Top, this.TitleRectangle.Width, this.TitleRectangle.Height, title); + } + } + if (this._LockOption) { + using (var fg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ForeColor), Style = SKPaintStyle.Fill, IsAntialias = true }) { + int n = this._Top + this._TitleHeight / 2 - 5; + canvas.DrawRect(this._Left + 4, n + 0, 2, 4, fg); + canvas.DrawRect(this._Left + 6, n + 0, 2, 2, fg); + canvas.DrawRect(this._Left + 8, n + 0, 2, 4, fg); + canvas.DrawRect(this._Left + 3, n + 4, 8, 6, fg); + } + } + if (this._LockLocation) { + using (var fg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ForeColor), Style = SKPaintStyle.Fill, IsAntialias = true }) { + int n = this._Top + this._TitleHeight / 2 - 5; + canvas.DrawRect(this.Right - 9, n, 4, 4, fg); + canvas.DrawRect(this.Right - 11, n + 4, 8, 2, fg); + canvas.DrawRect(this.Right - 8, n + 6, 2, 4, fg); + } + } + if (!string.IsNullOrEmpty(this._Title) && this._ForeColor.A != 0) { + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ForeColor), TextSize = Math.Max(10f, this._Font.Size), IsAntialias = true }) { + var fm = text.FontMetrics; + float y = this.TitleRectangle.Top + (this.TitleRectangle.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + float x = this.TitleRectangle.Left + (this.TitleRectangle.Width - text.MeasureText(this._Title)) / 2; + canvas.DrawText(this._Title, x, y, text); + } + } + }); } /// /// 绘制Node主体部分 除去标题部分 @@ -652,23 +647,21 @@ protected virtual void OnDrawBody(DrawingTools dt) { this.OnDrawOptionText(dt, op); } if (this._Controls.Count != 0) { //绘制子控件 - //将坐标原点与节点对齐 - //dt.Graphics.ResetTransform(); - dt.Graphics.TranslateTransform(this._Left, this._Top + this._TitleHeight); - Point pt = Point.Empty; //当前需要偏移的量 - Point pt_last = Point.Empty; //最后一个控件相对于节点的坐标 - 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); //将原点坐标移动至控件位置 - dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; - v.OnPaint(dt); + if (dt.Canvas != null) { + dt.Canvas.Save(); + dt.Canvas.Translate(this._Left, this._Top + this._TitleHeight); + Point pt = Point.Empty; + Point pt_last = Point.Empty; + 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.Canvas.Translate(pt.X, pt.Y); + v.OnPaint(dt); + } + dt.Canvas.Restore(); } - //dt.Graphics.TranslateTransform(-pt_last.X, -pt_last.Y); 还原坐标 - dt.Graphics.TranslateTransform(-this._Left - pt_last.X, -this._Top - this._TitleHeight - pt_last.Y); - //dt.Graphics. } } /// @@ -676,27 +669,24 @@ protected virtual void OnDrawBody(DrawingTools dt) { /// /// 绘制工具 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); //填充背景色 - - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //确定文本绘制所需大小 - 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) { //如果超过绘图区 则绘制部分 - 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); // + 表示超过绘图区 - } else { - m_sf.Alignment = StringAlignment.Near; - g.DrawString(this._MarkLines[0].Trim(), this._Font, brush, this._MarkRectangle, m_sf); + if (string.IsNullOrEmpty(this._Mark) || dt.Canvas == null) return; + using (var bg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._MarkColor), Style = SKPaintStyle.Fill, IsAntialias = false }) + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ForeColor), TextSize = Math.Max(10f, this._Font.Size), IsAntialias = true }) { + dt.Canvas.DrawRect(this._MarkRectangle.Left, this._MarkRectangle.Top, this._MarkRectangle.Width, this._MarkRectangle.Height, bg); + var fm = text.FontMetrics; + float textHeight = fm.Descent - fm.Ascent; + float textWidth = text.MeasureText(this.Mark ?? string.Empty); + if (textHeight > this._ItemHeight || textWidth > this._MarkRectangle.Width) { + string line = (this._MarkLines != null && this._MarkLines.Length > 0) ? this._MarkLines[0] : string.Empty; + float y = this._MarkRectangle.Top + (this._MarkRectangle.Height - textHeight) / 2f - fm.Ascent; + dt.Canvas.DrawText(line, this._MarkRectangle.Left + 2, y, text); + float plusW = text.MeasureText("+"); + dt.Canvas.DrawText("+", this._MarkRectangle.Right - plusW - 2, y, text); + } else { + string line = (this._MarkLines != null && this._MarkLines.Length > 0) ? this._MarkLines[0].Trim() : string.Empty; + float y = this._MarkRectangle.Top + (this._MarkRectangle.Height - textHeight) / 2f - fm.Ascent; + dt.Canvas.DrawText(line, this._MarkRectangle.Left + 2, y, text); + } } } /// @@ -705,30 +695,32 @@ protected internal virtual void OnDrawMark(DrawingTools dt) { /// 绘制工具 /// 指定的选项 protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op) { - Graphics g = dt.Graphics; - Pen pen = dt.Pen; - SolidBrush brush = dt.SolidBrush; + if (dt.Canvas == null) return; var t = typeof(object); - if (op.DotColor != Color.Transparent) //设置颜色 - 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; + SKColor fillColor; + SKColor strokeColor = SkiaDrawingHelper.ToSKColor(this.Owner.UnknownTypeColor); + bool isUnknown = op.DataType == t; + if (op.DotColor != Color.Transparent) { + fillColor = SkiaDrawingHelper.ToSKColor(op.DotColor); + } else { + var c = this.Owner.TypeColor.ContainsKey(op.DataType) ? this.Owner.TypeColor[op.DataType] : this.Owner.UnknownTypeColor; + fillColor = SkiaDrawingHelper.ToSKColor(c); + strokeColor = SkiaDrawingHelper.ToSKColor(this.Owner.UnknownTypeColor); } - if (op.IsSingle) { //单连接 圆形 - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - if (op.DataType == t) { //未知类型绘制 否则填充 - g.DrawEllipse(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); - } else - g.FillEllipse(brush, op.DotRectangle); - } else { //多连接 矩形 - 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); + using (var fill = new SKPaint { Color = fillColor, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var stroke = new SKPaint { Color = strokeColor, Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + if (op.IsSingle) { + if (isUnknown) { + dt.Canvas.DrawOval(op.DotRectangle.X + op.DotRectangle.Width / 2f, op.DotRectangle.Y + op.DotRectangle.Height / 2f, (op.DotRectangle.Width - 1) / 2f, (op.DotRectangle.Height - 1) / 2f, stroke); + } else { + dt.Canvas.DrawOval(op.DotRectangle.X + op.DotRectangle.Width / 2f, op.DotRectangle.Y + op.DotRectangle.Height / 2f, op.DotRectangle.Width / 2f, op.DotRectangle.Height / 2f, fill); + } + } else { + if (isUnknown) + dt.Canvas.DrawRect(op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1, stroke); + else + dt.Canvas.DrawRect(op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width, op.DotRectangle.Height, fill); + } } } /// @@ -737,15 +729,14 @@ protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op) { /// 绘制工具 /// 指定的选项 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); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(op.TextColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + var fm = text.FontMetrics; + float y = op.TextRectangle.Top + (op.TextRectangle.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + float x = op.IsInput ? op.TextRectangle.Left + 2 : op.TextRectangle.Right - text.MeasureText(op.Text) - 2; + canvas.DrawText(op.Text ?? string.Empty, x, y, text); + } + }); } /// /// 当计算Option连线点位置时候发生 @@ -774,7 +765,7 @@ protected virtual Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle /// /// 绘图面板 /// 计算出来的大小 - protected virtual Size GetDefaultNodeSize(Graphics g) { + protected virtual Size GetDefaultNodeSize() { int nInputHeight = 0, nOutputHeight = 0; foreach (STNodeOption op in this._InputOptions) nInputHeight += this._ItemHeight; foreach (STNodeOption op in this._OutputOptions) nOutputHeight += this._ItemHeight; @@ -783,16 +774,18 @@ protected virtual Size GetDefaultNodeSize(Graphics g) { 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); + float w = this.MeasureTextWidth(v.Text); + SizeF szf = new SizeF(w, this._Font.Height); 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); + float w = this.MeasureTextWidth(v.Text); + SizeF szf = new SizeF(w, this._Font.Height); 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 (!string.IsNullOrEmpty(this.Title)) szf_input = new SizeF(this.MeasureTextWidth(this.Title), this.Font.Height); if (szf_input.Width + 30 > nWidth) nWidth = (int)szf_input.Width + 30; return new Size(nWidth, nHeight); } @@ -803,10 +796,15 @@ protected virtual Size GetDefaultNodeSize(Graphics g) { /// /// 绘图面板 /// 计算后的区域 - protected virtual Rectangle OnBuildMarkRectangle(Graphics g) { + protected virtual Rectangle OnBuildMarkRectangle() { //if (string.IsNullOrEmpty(this._Mark)) return Rectangle.Empty; return new Rectangle(this._Left, this._Top - 30, this._Width, 20); } + private float MeasureTextWidth(string text) { + using (var paint = new SKPaint { TextSize = Math.Max(10f, this._Font.Size), IsAntialias = true }) { + return paint.MeasureText(text ?? string.Empty); + } + } /// /// 当需要保存时候 此Node有哪些需要额外保存的数据 /// 注意: 保存时并不会进行序列化 还原时候仅重新通过空参数构造器创建此Node @@ -974,7 +972,7 @@ protected internal virtual void OnMouseClick(MouseEventArgs e) { 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) { + if (m_ctrl_hover != null && m_ctrl_hover.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; } diff --git a/ST.Library.UI/NodeEditor/STNodeControl.cs b/ST.Library.UI/NodeEditor/STNodeControl.cs index 4bd9cca..e84f3cf 100644 --- a/ST.Library.UI/NodeEditor/STNodeControl.cs +++ b/ST.Library.UI/NodeEditor/STNodeControl.cs @@ -4,6 +4,7 @@ using System.Text; using System.Windows.Forms; using System.Drawing; +using SkiaSharp; /* MIT License @@ -195,15 +196,19 @@ public STNodeControl() { } 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 (dt.Canvas != null) { + using (var bg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._BackColor), Style = SKPaintStyle.Fill, IsAntialias = true }) { + dt.Canvas.DrawRect(0, 0, this.Width, this.Height, bg); + } + if (!string.IsNullOrEmpty(this._Text)) { + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ForeColor), TextSize = Math.Max(10f, this._Font.Size), IsAntialias = true }) { + var fm = text.FontMetrics; + float y = (this.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + float w = text.MeasureText(this._Text); + float x = (this.Width - w) / 2; + dt.Canvas.DrawText(this._Text, x, y, text); + } + } } if (this.Paint != null) this.Paint(this, new STNodeControlPaintEventArgs(dt)); } diff --git a/ST.Library.UI/NodeEditor/STNodeEditor.cs b/ST.Library.UI/NodeEditor/STNodeEditor.cs index 75bb669..f2d2ea8 100644 --- a/ST.Library.UI/NodeEditor/STNodeEditor.cs +++ b/ST.Library.UI/NodeEditor/STNodeEditor.cs @@ -11,6 +11,8 @@ using System.ComponentModel; using System.Reflection; using System.IO.Compression; +using SkiaSharp; +using SkiaSharp.Views.Desktop; /* MIT License @@ -44,7 +46,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ namespace ST.Library.UI.NodeEditor { - public class STNodeEditor : Control + public class STNodeEditor : SKControl { private const UInt32 WM_MOUSEHWHEEL = 0x020E; protected static readonly Type m_type_node = typeof(STNode); @@ -462,6 +464,7 @@ public STNodeEditor() { #region private fields -------------------------------------------------------------------------------------- private DrawingTools m_drawing_tools; + private SKCanvas m_canvas; private NodeFindInfo m_find = new NodeFindInfo(); private MagnetInfo m_mi = new MagnetInfo(); @@ -645,47 +648,76 @@ protected override void WndProc(ref Message m) { } 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; + protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) { + base.OnPaintSurface(e); + m_canvas = e.Surface.Canvas; + m_drawing_tools.Canvas = m_canvas; + m_canvas.Clear(SkiaDrawingHelper.ToSKColor(this.BackColor)); + this.RenderEditorToCanvas(m_canvas); + m_canvas = null; + } - if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, this.Width, this.Height); + private void RenderEditorToCanvas(SKCanvas canvas) { + if (canvas == null) return; + using (var bitmap = this.RenderEditorToBitmap()) { + canvas.DrawBitmap(bitmap, 0, 0); + } + } - g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //移动坐标系 - g.ScaleTransform(this._CanvasScale, this._CanvasScale); //缩放绘图表面 + private SKBitmap RenderEditorToBitmap() { + var bitmap = new SKBitmap(Math.Max(this.Width, 1), Math.Max(this.Height, 1), true); + using (var canvas = new SKCanvas(bitmap)) { + canvas.Clear(SkiaDrawingHelper.ToSKColor(this.BackColor)); + this.RenderEditorToCanvasLegacy(canvas); + } + return bitmap; + } - this.OnDrawConnectedLine(m_drawing_tools); - this.OnDrawNode(m_drawing_tools, this.ControlToCanvas(this.ClientRectangle)); + private void RenderEditorToCanvasLegacy(SKCanvas canvas) { + if (canvas == null) return; + m_canvas = canvas; + m_drawing_tools.Canvas = m_canvas; + if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, this.Width, this.Height); - if (m_ca == CanvasAction.ConnectOption) { //如果正在连线 - 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); - } - //重置绘图坐标 我认为除了节点以外的其它 修饰相关的绘制不应该在Canvas坐标系中绘制 而应该使用控件的坐标进行绘制 不然会受到缩放比影响 - g.ResetTransform(); + this.RenderLegacyNodeLayer(canvas); switch (m_ca) { - case CanvasAction.MoveNode: //移动过程中 绘制对齐参考线 + case CanvasAction.MoveNode: if (this._ShowMagnet && this._ActiveNode != null) this.OnDrawMagnet(m_drawing_tools, m_mi); break; - case CanvasAction.SelectRectangle: //绘制矩形选取 + case CanvasAction.SelectRectangle: this.OnDrawSelectedRectangle(m_drawing_tools, this.CanvasToControl(m_rect_select)); break; - case CanvasAction.DrawMarkDetails: //绘制标记信息详情 + case CanvasAction.DrawMarkDetails: if (!string.IsNullOrEmpty(m_find.Mark)) this.OnDrawMark(m_drawing_tools); break; } + return bitmap; + } if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, this.Size, m_lst_node_out); - this.OnDrawAlert(g); + this.OnDrawAlert(); + } + + private void RenderLegacyNodeLayer(SKCanvas canvas) { + if (canvas == null) return; + m_drawing_tools.Canvas = canvas; + canvas.Save(); + canvas.Translate(this._CanvasOffsetX, this._CanvasOffsetY); + canvas.Scale(this._CanvasScale, this._CanvasScale); + + this.OnDrawConnectedLine(m_drawing_tools); + this.OnDrawNode(m_drawing_tools, this.ControlToCanvas(this.ClientRectangle)); + + if (m_ca == CanvasAction.ConnectOption) { + using (var high = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._HighLineColor), Style = SKPaintStyle.Stroke, StrokeWidth = 2f, IsAntialias = true }) { + if (m_option_down.IsInput) + this.DrawBezier(canvas, high, m_pt_in_canvas, m_pt_dot_down, this._Curvature); + else + this.DrawBezier(canvas, high, m_pt_dot_down, m_pt_in_canvas, this._Curvature); + } + } + canvas.Restore(); } protected override void OnMouseDown(MouseEventArgs e) { @@ -979,21 +1011,19 @@ protected override void OnDragDrop(DragEventArgs drgevent) { /// 需要绘制宽度 /// 需要绘制高度 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);             //网格间的间隔 根据比例绘制 - 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); - //原点两天线 - 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); - } + if (m_canvas == null) return; + using (var p1 = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(30, this._GridColor)), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) + using (var p2 = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(65, this._GridColor)), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + float nIncrement = (20 * this._CanvasScale); + int n = 5 - (int)(this._CanvasOffsetX / nIncrement); + for (float f = this._CanvasOffsetX % nIncrement; f < nWidth; f += nIncrement) + m_canvas.DrawLine(f, 0, f, nHeight, (n++ % 5 == 0 ? p2 : p1)); + n = 5 - (int)(this._CanvasOffsetY / nIncrement); + for (float f = this._CanvasOffsetY % nIncrement; f < nHeight; f += nIncrement) + m_canvas.DrawLine(0, f, nWidth, f, (n++ % 5 == 0 ? p2 : p1)); + p1.Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(this._Nodes.Count == 0 ? 255 : 120, this._GridColor)); + m_canvas.DrawLine(this._CanvasOffsetX, 0, this._CanvasOffsetX, nHeight, p1); + m_canvas.DrawLine(0, this._CanvasOffsetY, nWidth, this._CanvasOffsetY, p1); } } /// @@ -1023,81 +1053,84 @@ protected virtual void OnDrawNodeBorder(DrawingTools dt, STNode node) { 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); + this.RenderBorder(m_canvas, node.Rectangle, img_border); + if (!string.IsNullOrEmpty(node.Mark)) this.RenderBorder(m_canvas, node.MarkRectangle, img_border); } /// /// 当绘制已连接路径时候发生 /// /// 绘制工具 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); + if (m_canvas == null) return; 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) //确定线条颜色 - 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) { //如果当前绘制需要重新建立已连接的路径缓存 - GraphicsPath gp = this.CreateBezierPath(op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + using (var hover = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(50, 0, 0, 0)), Style = SKPaintStyle.Stroke, StrokeWidth = 4f, IsAntialias = true }) + using (var line = new SKPaint { Color = SKColors.Cyan, Style = SKPaintStyle.Stroke, StrokeWidth = 2f, IsAntialias = true }) { + foreach (STNode n in this._Nodes) { + foreach (STNodeOption op in n.OutputOptions) { + if (op == STNodeOption.Empty) continue; + Color lineColor; + if (op.DotColor != Color.Transparent) lineColor = op.DotColor; + else if (op.DataType == t) lineColor = this._UnknownTypeColor; + else lineColor = this._TypeColor.ContainsKey(op.DataType) ? this._TypeColor[op.DataType] : this._UnknownTypeColor; + line.Color = SkiaDrawingHelper.ToSKColor(lineColor); + + foreach (var v in op.ConnectedOption) { + this.DrawBezier(m_canvas, hover, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + this.DrawBezier(m_canvas, line, 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 }); + if (m_is_buildpath) { + 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 }); + } + } + } + } + if (m_gp_hover != null) { + using (var hp = this.ToSKPath(m_gp_hover)) { + if (hp != null) { + hover.Color = SkiaDrawingHelper.ToSKColor(this._HighLineColor); + m_canvas.DrawPath(hp, hover); } } } } - m_p_line_hover.Color = this._HighLineColor; - if (m_gp_hover != null) { //如果当前有被悬停的连接路劲 则高亮绘制 - g.DrawPath(m_p_line_hover, m_gp_hover); - } - m_is_buildpath = false; //重置标志 下次绘制时候 不再重新建立路径缓存 + m_is_buildpath = false; } + /// /// 当绘制 Mark 详情信息时候发生 /// /// 绘制工具 protected virtual void OnDrawMark(DrawingTools dt) { - Graphics g = dt.Graphics; - SizeF sz = g.MeasureString(m_find.Mark, this.Font); //确认文字需要的大小 - 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并没有考虑文字的行距 所以这里高度自己计算 - - 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); //绘制背景区域 - 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++) { //绘制文字 - rect.Y = nY + i * (this.Font.Height + 4); - g.DrawString(m_find.MarkLines[i], this.Font, dt.SolidBrush, rect, m_sf); + if (m_canvas == null || string.IsNullOrEmpty(m_find.Mark)) return; + using (var textPaint = new SKPaint { TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + float textWidth = textPaint.MeasureText(m_find.Mark); + Rectangle rect = new Rectangle(m_pt_in_control.X + 15, + m_pt_in_control.Y + 10, + (int)Math.Ceiling(textWidth) + 6, + 4 + (this.Font.Height + 4) * m_find.MarkLines.Length); + + 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; + + using (var bg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._MarkBackColor), Style = SKPaintStyle.Fill, IsAntialias = false }) + using (var border = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(255, this._MarkBackColor)), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = false }) + using (var fg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._MarkForeColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + m_canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); + m_canvas.DrawRect(rect.Left, rect.Top, rect.Width - 1, rect.Height - 1, border); + + var fm = fg.FontMetrics; + float baseY = rect.Y + 2 - fm.Ascent; + float textX = rect.X + 4; + for (int i = 0; i < m_find.MarkLines.Length; i++) { + float y = baseY + i * (this.Font.Height + 4); + m_canvas.DrawText(m_find.MarkLines[i], textX, y, fg); + } + } } } /// @@ -1106,38 +1139,39 @@ protected virtual void OnDrawMark(DrawingTools dt) { /// 绘制工具 /// 匹配的磁铁信息 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); //移动坐标系 - g.ScaleTransform(this._CanvasScale, this._CanvasScale); //缩放绘图表面 - 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 (m_canvas == null || this._ActiveNode == null) return; + using (var line = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._MagnetColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = false }) + using (var fill = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(this._MagnetColor.A / 3, this._MagnetColor)), Style = SKPaintStyle.Fill, IsAntialias = false }) { + if (mi.XMatched) { + float x = this.CanvasToControl(mi.X, true); + m_canvas.DrawLine(x, 0, x, this.Height, line); + } + if (mi.YMatched) { + float y = this.CanvasToControl(mi.Y, false); + m_canvas.DrawLine(0, y, this.Width, y, line); + } + + m_canvas.Save(); + m_canvas.Translate(this._CanvasOffsetX, this._CanvasOffsetY); + m_canvas.Scale(this._CanvasScale, this._CanvasScale); + + if (mi.XMatched) { + foreach (STNode n in this._Nodes) { + if (n.Left == mi.X || n.Right == mi.X || n.Left + n.Width / 2 == mi.X) { + m_canvas.DrawRect(n.Left, n.Top, n.Width, n.Height, fill); + } } } - } - 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); + if (mi.YMatched) { + foreach (STNode n in this._Nodes) { + if (n.Top == mi.Y || n.Bottom == mi.Y || n.Top + n.Height / 2 == mi.Y) { + m_canvas.DrawRect(n.Left, n.Top, n.Width, n.Height, fill); + } } } + + m_canvas.Restore(); } - g.ResetTransform(); } /// /// 绘制选择的矩形区域 @@ -1145,12 +1179,14 @@ protected virtual void OnDrawMagnet(DrawingTools dt, MagnetInfo mi) { /// 绘制工具 /// 位于控件上的矩形区域 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)); + if (m_canvas == null) return; + var fill = Color.FromArgb(this._SelectedRectangleColor.A / 3, this._SelectedRectangleColor); + Rectangle rect = this.CanvasToControl(m_rect_select); + using (var stroke = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._SelectedRectangleColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) + using (var bg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(fill), Style = SKPaintStyle.Fill, IsAntialias = true }) { + m_canvas.DrawRect(rectf.Left, rectf.Top, rectf.Width, rectf.Height, stroke); + m_canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); + } } /// /// 绘制超出视觉区域的 Node 位置提示信息 @@ -1159,25 +1195,25 @@ protected virtual void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf /// 提示框边距 /// 超出视觉区域的 Node 位置信息 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) { //如果超出个数和集合个数一样多 则全部超出 绘制外切矩形 - g.FillRectangle(brush, this.CanvasToControl(this._CanvasValidBounds)); - } - g.FillRectangle(brush, 0, 0, 4, sz.Height); //绘制四边背景 - 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) { //绘制点 - 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); + if (m_canvas == null) return; + using (var back = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._LocationBackColor), Style = SKPaintStyle.Fill, IsAntialias = false }) + using (var fore = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._LocationForeColor), Style = SKPaintStyle.Fill, IsAntialias = false }) { + if (lstPts.Count == this._Nodes.Count && this._Nodes.Count != 0) { + Rectangle r = this.CanvasToControl(this._CanvasValidBounds); + m_canvas.DrawRect(r.Left, r.Top, r.Width, r.Height, back); + } + m_canvas.DrawRect(0, 0, 4, sz.Height, back); + m_canvas.DrawRect(sz.Width - 4, 0, 4, sz.Height, back); + m_canvas.DrawRect(4, 0, sz.Width - 8, 4, back); + m_canvas.DrawRect(4, sz.Height - 4, sz.Width - 8, 4, back); + foreach (var v in lstPts) { + 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; + m_canvas.DrawRect(pt.X, pt.Y, 4, 4, fore); + } } } /// @@ -1190,21 +1226,17 @@ protected virtual void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List信息背景色 /// 信息位置 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); + if (m_alpha_alert == 0 || m_canvas == null) return; + using (var bg = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(backColor), Style = SKPaintStyle.Fill, IsAntialias = false }) + using (var border = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(backColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = false }) + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(foreColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + m_canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); + m_canvas.DrawRect(rect.Left, rect.Top, rect.Width - 1, rect.Height - 1, border); + var fm = text.FontMetrics; + float x = rect.Left + (rect.Width - text.MeasureText(strText)) / 2f; + float y = rect.Top + (rect.Height - (fm.Descent - fm.Ascent)) / 2f - fm.Ascent; + m_canvas.DrawText(strText, x, y, text); + } } /// /// 获取提示信息需要绘制的矩形区域 @@ -1213,10 +1245,13 @@ protected virtual void OnDrawAlert(DrawingTools dt, Rectangle rect, string strTe /// 需要绘制文本 /// 信息位置 /// 矩形区域 - 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); + protected virtual Rectangle GetAlertRectangle(string strText, AlertLocation al) { + using (var text = new SKPaint { TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + var fm = text.FontMetrics; + float w = text.MeasureText(m_str_alert); + float h = fm.Descent - fm.Ascent; + Size sz = new Size((int)Math.Round(w + 10), (int)Math.Round(h + 4)); + Rectangle rect = new Rectangle(4, this.Height - sz.Height - 4, sz.Width, sz.Height); switch (al) { case AlertLocation.Left: @@ -1248,7 +1283,8 @@ protected virtual Rectangle GetAlertRectangle(Graphics g, string strText, AlertL rect.X = this.Width - sz.Width - 4; break; } - return rect; + return rect; + } } #endregion protected @@ -1262,8 +1298,8 @@ internal void BuildLinePath() { this.Invalidate(); } - internal void OnDrawAlert(Graphics g) { - m_rect_alert = this.GetAlertRectangle(g, m_str_alert, m_al); + internal void OnDrawAlert() { + m_rect_alert = this.GetAlertRectangle(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); @@ -1345,19 +1381,23 @@ private void ShowAlertThread() { } 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); - } + using (var bitmap = new SKBitmap(12, 12, true)) + using (var canvas = new SKCanvas(bitmap)) + using (var paint = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Fill }) { + var center = SkiaDrawingHelper.ToSKColor(Color.FromArgb(200, clr)); + var edge = SkiaDrawingHelper.ToSKColor(Color.FromArgb(10, clr)); + using (var shader = SKShader.CreateRadialGradient(new SKPoint(5.5f, 5.5f), 5.5f, + new[] { center, edge }, + new float[] { 0f, 1f }, SKShaderTileMode.Clamp)) { + paint.Shader = shader; + canvas.DrawCircle(5.5f, 5.5f, 5.5f, paint); + } + using (var image = SKImage.FromBitmap(bitmap)) + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + using (var ms = new MemoryStream(data.ToArray())) { + return Image.FromStream(ms); } } - return img; } private ConnectionStatus DisConnectionHover() { @@ -1490,18 +1530,19 @@ private MagnetInfo CheckMagnet(STNode node) { 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(SKCanvas canvas, SKPaint paint, PointF ptStart, PointF ptEnd, float f) { + this.DrawBezier(canvas, paint, 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) { + private void DrawBezier(SKCanvas canvas, SKPaint paint, float x1, float y1, float x2, float y2, float f) { + if (canvas == null || paint == null) return; 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); + using (var path = new SKPath()) { + path.MoveTo(x1, y1); + path.CubicTo(x1 + n, y1, x2 - n, y2, x2, y2); + canvas.DrawPath(path, paint); + } } private GraphicsPath CreateBezierPath(float x1, float y1, float x2, float y2, float f) { @@ -1517,25 +1558,35 @@ private GraphicsPath CreateBezierPath(float x1, float y1, float x2, float y2, fl return gp; } - private void RenderBorder(Graphics g, Rectangle rect, Image img) { - //填充四个角 - 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); - //四边 - 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); + private SKPath ToSKPath(GraphicsPath gp) { + if (gp == null) return null; + var points = gp.PathPoints; + if (points == null || points.Length < 2) return null; + var path = new SKPath(); + path.MoveTo(points[0].X, points[0].Y); + int i = 1; + while (i + 2 < points.Length) { + path.CubicTo(points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y, points[i + 2].X, points[i + 2].Y); + i += 3; + } + return path; + } + + private void RenderBorder(SKCanvas canvas, Rectangle rect, Image img) { + if (canvas == null || img == null) return; + using (var bmp = SkiaDrawingHelper.ToSKBitmap(img)) { + if (bmp == null) return; + //填充四个角 + canvas.DrawBitmap(bmp, new SKRect(0, 0, 5, 5), new SKRect(rect.X - 5, rect.Y - 5, rect.X, rect.Y)); + canvas.DrawBitmap(bmp, new SKRect(img.Width - 5, 0, img.Width, 5), new SKRect(rect.Right, rect.Y - 5, rect.Right + 5, rect.Y)); + canvas.DrawBitmap(bmp, new SKRect(0, img.Height - 5, 5, img.Height), new SKRect(rect.X - 5, rect.Bottom, rect.X, rect.Bottom + 5)); + canvas.DrawBitmap(bmp, new SKRect(img.Width - 5, img.Height - 5, img.Width, img.Height), new SKRect(rect.Right, rect.Bottom, rect.Right + 5, rect.Bottom + 5)); + //四边 + canvas.DrawBitmap(bmp, new SKRect(0, 5, 5, img.Height - 10), new SKRect(rect.X - 5, rect.Y, rect.X, rect.Bottom)); + canvas.DrawBitmap(bmp, new SKRect(5, 0, img.Width - 10, 5), new SKRect(rect.X, rect.Y - 5, rect.Right, rect.Y)); + canvas.DrawBitmap(bmp, new SKRect(img.Width - 5, 5, img.Width, img.Height - 10), new SKRect(rect.Right, rect.Y, rect.Right + 5, rect.Bottom)); + canvas.DrawBitmap(bmp, new SKRect(5, img.Height - 5, img.Width - 10, img.Height), new SKRect(rect.X, rect.Bottom, rect.Right, rect.Bottom + 5)); + } } #endregion private @@ -1786,22 +1837,27 @@ private static bool CanFindNodePath(STNode nodeStart, STNode nodeFind, HashSet图像 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; + using (var bitmap = new SKBitmap((int)(rect.Width * fScale), (int)(rect.Height * fScale), true)) + using (var canvas = new SKCanvas(bitmap)) { + canvas.Clear(SkiaDrawingHelper.ToSKColor(this.BackColor)); + m_canvas = canvas; + m_drawing_tools.Canvas = canvas; + if (fScale != 1f) canvas.Scale(fScale, fScale); if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, rect.Width, rect.Height); - g.TranslateTransform(-rect.X, -rect.Y); //移动坐标系 + canvas.Translate(-rect.X, -rect.Y); 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); + canvas.ResetMatrix(); + + if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, new Size(bitmap.Width, bitmap.Height), m_lst_node_out); + using (var image = SKImage.FromBitmap(bitmap)) + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + using (var ms = new MemoryStream(data.ToArray())) { + m_canvas = null; + return Image.FromStream(ms); + } } - return img; } /// /// 保存画布中的类容到文件中 diff --git a/ST.Library.UI/NodeEditor/STNodeEditorDataType.cs b/ST.Library.UI/NodeEditor/STNodeEditorDataType.cs index d84fa18..35b8156 100644 --- a/ST.Library.UI/NodeEditor/STNodeEditorDataType.cs +++ b/ST.Library.UI/NodeEditor/STNodeEditorDataType.cs @@ -4,6 +4,7 @@ using System.Text; using System.ComponentModel; using System.Drawing; +using SkiaSharp; namespace ST.Library.UI.NodeEditor { @@ -96,7 +97,7 @@ public enum AlertLocation public struct DrawingTools { - public Graphics Graphics; + public SKCanvas Canvas; public Pen Pen; public SolidBrush SolidBrush; } diff --git a/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs b/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs index c4a1041..62c91d4 100644 --- a/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs +++ b/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs @@ -5,12 +5,14 @@ using System.Windows.Forms; using System.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Desktop; using System.Runtime.InteropServices; using System.ComponentModel; namespace ST.Library.UI.NodeEditor { - public class STNodeEditorPannel : Control + public class STNodeEditorPannel : SKControl { private bool _LeftLayout = true; /// @@ -123,7 +125,6 @@ public STNodePropertyGrid PropertyGrid { 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(); @@ -163,7 +164,6 @@ public STNodeEditorPannel() { 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); @@ -208,25 +208,27 @@ protected override void SetBoundsCore(int x, int y, int width, int height, Bound 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; + protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) { + base.OnPaintSurface(e); + var splitColor = SkiaDrawingHelper.ToSKColor(this._SplitLineColor); 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); + var handleColor = SkiaDrawingHelper.ToSKColor(this._HandleLineColor); + var canvas = e.Surface.Canvas; + canvas.Clear(SkiaDrawingHelper.ToSKColor(this.BackColor)); + using (var split = new SKPaint { Color = splitColor, StrokeWidth = 3, IsAntialias = true, Style = SKPaintStyle.Stroke }) + using (var handle = new SKPaint { Color = handleColor, StrokeWidth = 1, IsAntialias = true, Style = SKPaintStyle.Stroke }) { + canvas.DrawLine(this._X, 0, this._X, this.Height, split); + int nX; + if (this._LeftLayout) { + canvas.DrawLine(0, this._Y, this._X - 1, this._Y, split); + nX = this._X / 2; + } else { + canvas.DrawLine(this._X + 2, this._Y, this.Width, this._Y, split); + nX = this._X + (this.Width - this._X) / 2; + } + canvas.DrawLine(this._X, this._Y - 10, this._X, this._Y + 10, handle); + canvas.DrawLine(nX - 10, this._Y, nX + 10, this._Y, handle); + } } private void SetLocation() { diff --git a/ST.Library.UI/NodeEditor/STNodeHub.cs b/ST.Library.UI/NodeEditor/STNodeHub.cs index 32bb171..608ebc3 100644 --- a/ST.Library.UI/NodeEditor/STNodeHub.cs +++ b/ST.Library.UI/NodeEditor/STNodeHub.cs @@ -57,9 +57,7 @@ public STNodeHub(bool bSingle, string strTextIn, string strTextOut) { protected override void OnOwnerChanged() { base.OnOwnerChanged(); if (this.Owner == null) return; - using (Graphics g = this.Owner.CreateGraphics()) { - this.Width = base.GetDefaultNodeSize(g).Width; - } + this.Width = base.GetDefaultNodeSize().Width; } private void Addhub() { diff --git a/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs b/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs index 7151bfd..10bab40 100644 --- a/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs +++ b/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs @@ -4,6 +4,7 @@ using System.Text; using System.Drawing; +using SkiaSharp; using System.Reflection; using System.Windows.Forms; @@ -263,25 +264,31 @@ protected internal virtual void OnSetValueError(Exception ex) { /// /// 绘制工具 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) - }); - } + var textValue = this.GetStringFromValue(); + var backColor = SkiaDrawingHelper.ToSKColor(ctrl.ItemValueBackColor); + var textColor = SkiaDrawingHelper.ToSKColor(this.Control.ForeColor); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + using (var fill = new SKPaint { Color = backColor, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var text = new SKPaint { Color = textColor, TextSize = Math.Max(10f, ctrl.Font.Size), IsAntialias = true }) { + canvas.DrawRect(rect.Left, rect.Top, rect.Width + 1, rect.Height + 1, fill); + var metrics = text.FontMetrics; + float textY = rect.Top + (rect.Height - (metrics.Descent - metrics.Ascent)) / 2 - metrics.Ascent; + canvas.DrawText(textValue ?? string.Empty, rect.Left + 2, textY, text); + if (this.PropertyInfo.PropertyType.IsEnum || this.PropertyInfo.PropertyType == m_t_bool) { + using (var arrow = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var path = new SKPath()) { + path.MoveTo(rect.Right - 13, rect.Top + rect.Height / 2 - 2); + path.LineTo(rect.Right - 4, rect.Top + rect.Height / 2 - 2); + path.LineTo(rect.Right - 9, rect.Top + rect.Height / 2 + 3); + path.Close(); + canvas.DrawPath(path, arrow); + } + } + } + }); } /// /// 当鼠标进入属性值所在区域时候发生 diff --git a/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs b/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs index 9b07e37..99d29ee 100644 --- a/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs +++ b/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs @@ -7,6 +7,8 @@ using System.Reflection; using System.Windows.Forms; using System.ComponentModel; +using SkiaSharp; +using SkiaSharp.Views.Desktop; /* MIT License @@ -43,7 +45,7 @@ namespace ST.Library.UI.NodeEditor /// /// STNode节点属性编辑器 /// - public class STNodePropertyGrid : Control + public class STNodePropertyGrid : SKControl { #region properties ========== @@ -261,6 +263,7 @@ public bool ReadOnlyModel { private SolidBrush m_brush; private StringFormat m_sf; private DrawingTools m_dt; + private SKCanvas m_canvas; /// /// 构造一个节点属性编辑器 @@ -325,35 +328,35 @@ private STNodeAttribute GetNodeAttribute(STNode node) { private void SetItemRectangle() { int nw_p = 0, nw_h = 0; - using (Graphics g = this.CreateGraphics()) { + using (var paint = new SKPaint { TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { 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); + float w = paint.MeasureText(v.Name ?? string.Empty); + if (w > nw_p) nw_p = (int)Math.Ceiling(w); } 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); + float w = paint.MeasureText(m_KeysString[i] ?? string.Empty); + if (w > nw_h) nw_h = (int)Math.Ceiling(w); } - 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; } + 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 @@ -364,31 +367,19 @@ private void SetItemRectangle() { /// 当控件重绘时候发生 /// /// 事件参数 - 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; + protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) { + base.OnPaintSurface(e); + m_canvas = e.Surface.Canvas; + m_canvas.Clear(SkiaDrawingHelper.ToSKColor(this.BackColor)); + if (this._InfoFirstOnDraw) { 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); - } + 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); + m_canvas = null; } /// /// 当鼠标在控件上移动时候发生 @@ -533,33 +524,29 @@ protected override void OnResize(EventArgs e) { /// 目标属性选项描述器 /// 选项所在索引 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); + if (m_canvas == null) return; + using (var fill = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this.ForeColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + fill.Color = SkiaDrawingHelper.ToSKColor((nIndex % 2 == 0) ? m_clr_item_1 : m_clr_item_2); + m_canvas.DrawRect(item.Rectangle.Left, item.Rectangle.Top, item.Rectangle.Width, item.Rectangle.Height, fill); + if (item == m_item_hover || item == m_item_selected) { + fill.Color = SkiaDrawingHelper.ToSKColor(this._ItemHoverColor); + m_canvas.DrawRect(item.Rectangle.Left, item.Rectangle.Top, item.Rectangle.Width, item.Rectangle.Height, fill); + } + if (m_item_selected == item) { + m_canvas.DrawRect(item.Rectangle.X, item.Rectangle.Y, 5, item.Rectangle.Height, fill); + fill.Color = SkiaDrawingHelper.ToSKColor((this._AutoColor && this._STNode != null) ? this._STNode.TitleColor : this._ItemSelectedColor); + m_canvas.DrawRect(item.Rectangle.X, item.Rectangle.Y + 4, 5, item.Rectangle.Height - 8, fill); + } + var fm = text.FontMetrics; + float y = item.RectangleL.Top + (item.RectangleL.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + float x = item.RectangleL.Right - text.MeasureText(item.Name ?? string.Empty) - 2; + m_canvas.DrawText(item.Name ?? string.Empty, x, y, text); } - 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); + if (this._ReadOnlyModel && m_canvas != null) { + using (var overlay = new SKPaint { Color = new SKColor(125, 125, 125, 125), Style = SKPaintStyle.Fill, IsAntialias = true }) + m_canvas.DrawRect(item.RectangleR.Left, item.RectangleR.Top, item.RectangleR.Width, item.RectangleR.Height, overlay); } } /// @@ -567,159 +554,79 @@ protected virtual void OnDrawPropertyItem(DrawingTools dt, STNodePropertyDescrip /// /// 绘制工具 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); + if (m_canvas == null) return; + using (var fill = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var text = new SKPaint { TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true, Style = SKPaintStyle.Fill }) { + fill.Color = SkiaDrawingHelper.ToSKColor(this._AutoColor ? (this._STNode == null ? this._TitleColor : this._STNode.TitleColor) : this._TitleColor); + m_canvas.DrawRect(m_rect_title.Left, m_rect_title.Top, m_rect_title.Width, m_rect_title.Height, fill); + text.Color = SkiaDrawingHelper.ToSKColor(this._STNode == null ? this.ForeColor : this._STNode.ForeColor); + var t = this._STNode == null ? this.Text : this._STNode.Title; + var fm = text.FontMetrics; + float y = m_rect_title.Top + (m_rect_title.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + float x = m_rect_title.Left + (m_rect_title.Width - text.MeasureText(t ?? string.Empty)) / 2; + m_canvas.DrawText(t ?? string.Empty, x, y, text); } - //是否绘制面板切换按钮 - 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 ), - }); } /// /// 当需要绘制属性描述信息时发生 /// /// 绘制工具 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); + if (string.IsNullOrEmpty(m_str_desc) || m_canvas == null) return; + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this.ForeColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) + using (var fill = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._DescriptionColor), Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var stroke = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._DescriptionColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + var bounds = new SKRect(); + text.MeasureText(m_str_desc, ref bounds); + Rectangle rect_desc = new Rectangle(0, this.Height - (int)Math.Ceiling(bounds.Height) - 8, this.Width, (int)Math.Ceiling(bounds.Height) + 8); + m_canvas.DrawRect(rect_desc.Left, rect_desc.Top, rect_desc.Width, rect_desc.Height, fill); + m_canvas.DrawRect(rect_desc.Left, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1, stroke); + var fm = text.FontMetrics; + float y = rect_desc.Top + (rect_desc.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + m_canvas.DrawText(m_str_desc, rect_desc.Left + 4, y, text); + } } /// /// 当需要绘制错误信息时发生 /// /// 绘制工具 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); + if (string.IsNullOrEmpty(m_str_err) || m_canvas == null) return; + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this.ForeColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) + using (var fill = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ErrorColor), Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var stroke = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._ErrorColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + var bounds = new SKRect(); + text.MeasureText(m_str_err, ref bounds); + Rectangle rect_desc = new Rectangle(0, 0, this.Width, (int)Math.Ceiling(bounds.Height) + 8); + m_canvas.DrawRect(rect_desc.Left, rect_desc.Top, rect_desc.Width, rect_desc.Height, fill); + m_canvas.DrawRect(rect_desc.Left, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1, stroke); + var fm = text.FontMetrics; + float y = rect_desc.Top + (rect_desc.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + m_canvas.DrawText(m_str_err, rect_desc.Left + 4, y, text); + } } /// /// 当绘制节点信息时候发生 /// /// 绘制工具 protected virtual void OnDrawInfo(DrawingTools dt) { - if (m_node_attribute == null) return; + if (m_node_attribute == null || m_canvas == 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); + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this.ForeColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) + using (var textDim = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(this.ForeColor.A / 2, this.ForeColor)), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) + using (var fill = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true }) { + int top = this._ShowTitle ? m_nTitleHeight : 0; + int y = top; + void row(SKColor bg,string key,string val,SKPaint vp){ + fill.Color=bg; m_canvas.DrawRect(0,y,this.Width,m_item_height,fill); + var fm=text.FontMetrics; float ty=y+(m_item_height-(fm.Descent-fm.Ascent))/2-fm.Ascent; + m_canvas.DrawText(key,2,ty,text); m_canvas.DrawText(val??string.Empty,m_nInfoLeft,ty,vp); y+=m_item_height; + } + row(SkiaDrawingHelper.ToSKColor(m_clr_item_2),m_KeysString[0],attr.Author,textDim); + row(SkiaDrawingHelper.ToSKColor(m_clr_item_1),m_KeysString[1],attr.Mail,textDim); + row(SkiaDrawingHelper.ToSKColor(m_clr_item_2),m_KeysString[2],attr.Link,new SKPaint{Color=SKColors.CornflowerBlue,TextSize=text.TextSize,IsAntialias=true}); + m_nInfoVHeight = y + m_item_height; } - m_nInfoVHeight = rect.Bottom; } /// /// 当在属性面板鼠标点下时候发生 diff --git a/ST.Library.UI/NodeEditor/STNodeTreeView.cs b/ST.Library.UI/NodeEditor/STNodeTreeView.cs index 6b40ee3..28131ec 100644 --- a/ST.Library.UI/NodeEditor/STNodeTreeView.cs +++ b/ST.Library.UI/NodeEditor/STNodeTreeView.cs @@ -8,6 +8,8 @@ using System.Windows.Forms; using System.ComponentModel; using System.Collections; +using SkiaSharp; +using SkiaSharp.Views.Desktop; /* MIT License @@ -41,7 +43,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ namespace ST.Library.UI.NodeEditor { - public class STNodeTreeView : Control + public class STNodeTreeView : SKControl { private Color _ItemBackColor = Color.FromArgb(255, 45, 45, 45); /// @@ -195,6 +197,7 @@ public STNodePropertyGrid PropertyGrid { private SolidBrush m_brush; private StringFormat m_sf; private DrawingTools m_dt; + private SKCanvas m_canvas; 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); @@ -366,21 +369,22 @@ protected override void OnResize(EventArgs e) { m_rect_clear = new Rectangle(this.Width - 20, 9, 12, 12); } - protected override void OnPaint(PaintEventArgs e) { - base.OnPaint(e); + protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) { + base.OnPaintSurface(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); + m_canvas = e.Surface.Canvas; + m_canvas.Clear(SkiaDrawingHelper.ToSKColor(this.BackColor)); + m_canvas.Save(); + m_canvas.Translate(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(); + m_canvas.Restore(); this.OnDrawSearch(m_dt); + m_canvas = null; } protected override void OnMouseMove(MouseEventArgs e) { @@ -478,24 +482,20 @@ protected override void OnMouseWheel(MouseEventArgs e) { /// /// 绘制工具 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; + if (m_canvas == null) return; + using (var title = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._TitleColor), Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var box = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(m_tbx.BackColor), Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var stroke = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this.ForeColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + m_canvas.DrawRect(0, 0, this.Width, m_nItemHeight, title); + m_canvas.DrawRect(5, 5, this.Width - 10, m_nItemHeight - 10, box); + if (string.IsNullOrEmpty(m_str_search)) { + m_canvas.DrawOval(new SKRect(this.Width - 17, 8, this.Width - 9, 16), stroke); + m_canvas.DrawLine(this.Width - 13, 17, this.Width - 13, m_nItemHeight - 9, stroke); + } else { + m_canvas.DrawOval(new SKRect(this.Width - 20, 9, this.Width - 10, 19), stroke); + m_canvas.DrawLine(this.Width - 18, 11, this.Width - 12, 17, stroke); + m_canvas.DrawLine(this.Width - 12, 11, this.Width - 18, 17, stroke); + } } } /// @@ -507,7 +507,6 @@ protected virtual void OnDrawSearch(DrawingTools dt) { /// 当前位于第几级子集合 /// 已经绘制个数 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) @@ -534,23 +533,26 @@ protected virtual int OnStartDrawItem(DrawingTools dt, STNodeTreeCollection Item /// 已经绘制个数的计数器 /// 当前位于第几级子集合 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); + if (m_canvas == null) return; + using (var fill = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var line = new SKPaint { Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + fill.Color = SkiaDrawingHelper.ToSKColor(nCounter % 2 == 0 ? m_clr_item_1 : m_clr_item_2); + m_canvas.DrawRect(items.DisplayRectangle.Left, items.DisplayRectangle.Top, items.DisplayRectangle.Width, items.DisplayRectangle.Height, fill); + if (items == m_item_hover) { + fill.Color = SkiaDrawingHelper.ToSKColor(this._ItemHoverColor); + m_canvas.DrawRect(items.DisplayRectangle.Left, items.DisplayRectangle.Top, items.DisplayRectangle.Width, items.DisplayRectangle.Height, fill); } + Rectangle rect = new Rectangle(45 + nLevel * 10, items.SwitchRectangle.Top, this.Width - 45 - nLevel * 10, m_nItemHeight); + line.Color = SkiaDrawingHelper.ToSKColor(Color.FromArgb(100, 125, 125, 125)); + m_canvas.DrawLine(9, items.SwitchRectangle.Top + m_nItemHeight / 2, items.SwitchRectangle.Left + 19, items.SwitchRectangle.Top + m_nItemHeight / 2, line); + if (nCounter != 0) { + for (int i = 0; i <= nLevel; i++) { + m_canvas.DrawLine(9 + i * 10, items.SwitchRectangle.Top - m_nItemHeight / 2, 9 + i * 10, items.SwitchRectangle.Top + m_nItemHeight / 2 - 1, line); + } + } + this.OnDrawItemText(dt, items, rect); + this.OnDrawItemIcon(dt, items, rect); } - this.OnDrawItemText(dt, items, rect); - this.OnDrawItemIcon(dt, items, rect); } /// /// 当绘制树节点展开与关闭开关时候发生 @@ -558,15 +560,15 @@ protected virtual void OnDrawItem(DrawingTools dt, STNodeTreeCollection items, i /// 绘制工具 /// 当前需要绘制的集合 protected virtual void OnDrawSwitch(DrawingTools dt, STNodeTreeCollection items) { - Graphics g = dt.Graphics; + if (m_canvas == null) return; 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); + using (var stroke = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._SwitchColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + int nT = items.SwitchRectangle.Y + m_nItemHeight / 2 - 4; + m_canvas.DrawRect(items.SwitchRectangle.Left, nT, 8, 8, stroke); + m_canvas.DrawLine(items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4, stroke); + if (items.IsOpen) return; + m_canvas.DrawLine(items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7, stroke); + } //if (items.IsOpen) { // //g.FillPolygon(m_brush, new Point[]{ // // new Point(items.DotRectangle.Left + 0, items.DotRectangle.Top + m_nItemHeight / 2 - 2), @@ -594,28 +596,30 @@ protected virtual void OnDrawSwitch(DrawingTools dt, STNodeTreeCollection items) /// 当前需要绘制的集合 /// 文本域所在矩形区域 protected virtual void OnDrawItemText(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) { - Graphics g = dt.Graphics; + if (m_canvas == null) return; 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; + using (var textNormal = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 2 / 3, this.ForeColor) : this.ForeColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) + using (var textHi = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._HightLightTextColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + var fm = textNormal.FontMetrics; + float y = rect.Top + (rect.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + string name = items.Name ?? string.Empty; + if (!string.IsNullOrEmpty(m_str_search)) { + int idx = items.NameLower.IndexOf(m_str_search); + if (idx >= 0) { + string pre = name.Substring(0, idx); + string hit = name.Substring(idx, Math.Min(m_str_search.Length, name.Length - idx)); + string suf = name.Substring(idx + hit.Length); + float x = rect.Left; + m_canvas.DrawText(pre, x, y, textNormal); + x += textNormal.MeasureText(pre); + m_canvas.DrawText(hit, x, y, textHi); + x += textHi.MeasureText(hit); + m_canvas.DrawText(suf, x, y, textNormal); + return; + } } + m_canvas.DrawText(name, rect.Left, y, textNormal); } - 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); } /// /// 当绘制树节点图标时候发生 @@ -624,43 +628,52 @@ protected virtual void OnDrawItemText(DrawingTools dt, STNodeTreeCollection item /// 当前需要绘制的集合 /// 文本域所在矩形区域 protected virtual void OnDrawItemIcon(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) { - Graphics g = dt.Graphics; + if (m_canvas == null) return; 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); + using (var stroke = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._AutoColor ? items.STNodeTypeColor : Color.DarkCyan), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) + using (var fill = new SKPaint { Color = SKColors.LightGray, Style = SKPaintStyle.Fill, IsAntialias = true }) { + m_canvas.DrawRect(rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10, stroke); + m_canvas.DrawRect(rect.Left - 17, rect.Top + m_nItemHeight / 2 - 2, 5, 5, fill); + m_canvas.DrawRect(rect.Left - 6, rect.Top + m_nItemHeight / 2 - 2, 5, 5, fill); + if (m_item_hover == items && m_bHoverInfo) { + fill.Color = SkiaDrawingHelper.ToSKColor(this.BackColor); + m_canvas.DrawRect(items.InfoRectangle.Left, items.InfoRectangle.Top, items.InfoRectangle.Width, items.InfoRectangle.Height, fill); + } + } + using (var info = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._AutoColor ? items.STNodeTypeColor : this._InfoButtonColor), Style = SKPaintStyle.Stroke, StrokeWidth = 2, IsAntialias = true }) { + m_canvas.DrawLine(items.InfoRectangle.X + 4, items.InfoRectangle.Y + 3, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 3, info); + m_canvas.DrawLine(items.InfoRectangle.X + 4, items.InfoRectangle.Y + 6, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 6, info); + m_canvas.DrawLine(items.InfoRectangle.X + 4, items.InfoRectangle.Y + 11, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 11, info); + m_canvas.DrawLine(items.InfoRectangle.X + 7, items.InfoRectangle.Y + 7, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 10, info); + } + using (var box = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._AutoColor ? items.STNodeTypeColor : this._InfoButtonColor), Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + m_canvas.DrawRect(items.InfoRectangle.X, items.InfoRectangle.Y, items.InfoRectangle.Width - 1, items.InfoRectangle.Height - 1, box); } - 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)); + using (var stroke = new SKPaint { Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { + if (items.IsLibraryRoot) { + Rectangle rect_box = new Rectangle(rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10); + stroke.Color = SKColors.Gray; + m_canvas.DrawRect(rect_box.Left, rect_box.Top, rect_box.Width, rect_box.Height, stroke); + stroke.Color = SKColors.Cyan; + m_canvas.DrawLine(rect_box.X - 2, rect_box.Top, rect_box.X + 2, rect_box.Top, stroke); + m_canvas.DrawLine(rect_box.X, rect_box.Y - 2, rect_box.X, rect_box.Y + 2, stroke); + m_canvas.DrawLine(rect_box.Right - 2, rect_box.Bottom, rect_box.Right + 2, rect_box.Bottom, stroke); + m_canvas.DrawLine(rect_box.Right, rect_box.Bottom - 2, rect_box.Right, rect_box.Bottom + 2, stroke); + } else { + stroke.Color = SKColors.Goldenrod; + m_canvas.DrawRect(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 6, 8, 3, stroke); + m_canvas.DrawRect(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 3, 13, 9, stroke); + } } 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; + using (var text = new SKPaint { Color = SkiaDrawingHelper.ToSKColor(this._FolderCountColor), TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + string t = "[" + items.STNodeCount.ToString() + "]"; + float w = text.MeasureText(t); + var fm = text.FontMetrics; + float y = rect.Top + (rect.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; + m_canvas.DrawText(t, rect.Right - w - 4, y, text); + } } } diff --git a/ST.Library.UI/NodeEditor/SkiaDrawingHelper.cs b/ST.Library.UI/NodeEditor/SkiaDrawingHelper.cs new file mode 100644 index 0000000..f5c53d5 --- /dev/null +++ b/ST.Library.UI/NodeEditor/SkiaDrawingHelper.cs @@ -0,0 +1,29 @@ +using System; +using System.Drawing; +using SkiaSharp; + +namespace ST.Library.UI.NodeEditor { + public static class SkiaDrawingHelper { + public static SKColor ToSKColor(Color color) { + return new SKColor(color.R, color.G, color.B, color.A); + } + + public static SKRect ToSKRect(Rectangle rect) { + return new SKRect(rect.Left, rect.Top, rect.Right, rect.Bottom); + } + + public static SKBitmap ToSKBitmap(Image image) { + if (image == null) return null; + using (var ms = new System.IO.MemoryStream()) { + image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Position = 0; + return SKBitmap.Decode(ms); + } + } + + public static void RenderToCanvas(SKCanvas canvas, Action renderAction) { + if (canvas == null || renderAction == null) return; + renderAction(canvas); + } + } +} diff --git a/ST.Library.UI/ST.Library.UI.csproj b/ST.Library.UI/ST.Library.UI.csproj index 92a545a..fb6bb9b 100644 --- a/ST.Library.UI/ST.Library.UI.csproj +++ b/ST.Library.UI/ST.Library.UI.csproj @@ -1,87 +1,15 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {EFFCC270-4999-4077-A543-56CCCCE92147} - Library - Properties - ST.Library.UI - ST.Library.UI - v3.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AnyCPU - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\ST.Library.UI.XML - true - - - - - - - - - - - - - Form - - - Form - - - Component - - - Component - - - - Form - - - - - - - - Component - - - - - - - - Component - - - - - \ No newline at end of file + + + net10.0-windows + true + disable + disable + false + preview + + + + + + + diff --git a/V3_CN.md b/V3_CN.md new file mode 100644 index 0000000..e4d9725 --- /dev/null +++ b/V3_CN.md @@ -0,0 +1,81 @@ +由于`3.0`版本可能部分代码会被重构,所以决定开一个新的项目。但是项目目前并未上传`GitHub`。将在完成时候上传。 + +虽然用户量不多,但是感谢那些一直在使用`STNodeEditor`的用户,在他们那里作者得到了一些反馈。将做如下调整: + +|内容|状态|完成时间|备注| +|:---|:---|:---|:---| +|增加高DPI支持 |✅ |2022-09-12|创建了 `STGraphics`| +|添加json格式序列化文件 |✅ |2022-09-30|添加新项目 [STJson](https://github.com/DebugST/STJson)| +|添加`STNodeEditorCanvas` |☑️ |开始|-| +|添加缩略图 |☑️ |-|-| +|节点选项悬浮提示信息 |☑️ |-|-| +|修复已知bug |☑️ |-|-| + +添加控件支持: + +|内容|状态|完成时间|备注| +|:---|:---|:---|:---| +|Panel |☑️ |-|-| +|lable |☑️ |-|-| +|button |☑️ |-|-| +|textbox |☑️ |-|-| +|listview |☑️ |-|-| +|chekcbox |☑️ |-|-| +|radiobutton |☑️ |-|-| +|combobox |☑️ |-|-| +|groupbox |☑️ |-|-| +|picturebox |☑️ |-|-| +|progressbar |☑️ |-|-| +|trackbar |☑️ |-|-| +|NumericUpDown |☑️ |-|-| + +控件将尽可能保持`WinForm`的使用习惯。 + +添加`STNodeEditorCanvas.cs` + +替代原本的`STNodeEditor.cs` + +```cs +var canvas = new STNodeEditorCanvas("layer_name"); +canvas.Nodes.add(new STNode_1()); +canvas.Nodes.add(new STNode_2()); +STNodeEditor_1.Canvas = canvas; +// STNodeEditor.Layers.Add(canvas) 此方式先待定 +/* + 这样做的目的是想实现类似TabControl的效果,用户可能有多个画布需要加载, + 所以他不得不添加多个STNodeEditor做切换。所以用STNodeEditorCanvas代替原本的STNodeEditor + 而STNodeEditor仅仅作为一个画布容器 +*/ +``` + +添加`STNodeSpy.cs` + +虽然`STNodeEditor`可以通过选项点颜色来区分数据类型,但是不排除会出现同一个颜色不同数据类型的情况,尤其是节点并非一个人开发的情况。 + +`STNodeSpy`作为一个内置节点提供就像`STNodeHub`一样,它的使用方式将会和`SPY++`类似。 + + +添加`STNodeGroup.cs` + +目前的`STNodeEditor`并没有分组的功能,作者准备尝试使用`Blender`一样的分组方式,毕竟作者确实很喜欢`Blender`。将分组作为一个节点使用。 + +```cs +var group = new STNodeGroup("group_name"); +group.Nodes.Add(new STNode_1()); +group.Nodes.Add(new STNode_2()); +var layer = new STNodeEditorLayer("layer_name"); +layder.Nodes.Add(group); +/* + group 具有可编辑属性,它将以普通节点的形式存在于layer中。但是它可以被展开 + 当group被展开时,它将变成一个独立的layer,可以进行节点的添加和删除。 + 可以参考Blender的group,可以理解为将整个画布的最开始的输入和最终的输出作为一个节点的输入和输出。 +*/ +``` + +以上仅供参考,最终效果请以实物为准。😏😏😏😏😏(毕竟作者是个死咸鱼,不排除摆烂、虚假宣传的可能)。 + +如上面的某一个功能已经完成则会在旁边标记一个时间 比如: + +(2023-12-21)`增加高DPI支持` + +如果你有什么想法可以联系咸鱼作者:2212233137@qq.com \ No newline at end of file diff --git a/V3_EN.md b/V3_EN.md new file mode 100644 index 0000000..8348227 --- /dev/null +++ b/V3_EN.md @@ -0,0 +1,81 @@ +Since the `3.0` version may be partially refactored, it was decided to start a new project. But the project is not currently uploaded to `GitHub`. Will upload when finished. + +Although there are not many users, thanks to those who have been using `STNodeEditor`, where the author got some feedback. The following adjustments will be made: + +|Items|Status|Complete time|Note| +|:---|:---|:---|:---| +|Add high DPI support |✅ |2022-09-12|Create `STGraphics`| +|Add json format serialization file |✅ |2022-09-30|Add new project [STJson](https://github.com/DebugST/STJson)| +|Add `STNodeEditorCanvas` |☑️ |start|-| +|Add mini-map |☑️ |-|-| +|Node option hover hint text |☑️ |-|-| +|Add mini-map |☑️ |-|-| + +Add Controls: + +|Items|Status|Complete time|Note| +|:---|:---|:---|:---| +|Panel |☑️ |-|-| +|lable |☑️ |-|-| +|button |☑️ |-|-| +|textbox |☑️ |-|-| +|listview |☑️ |-|-| +|chekcbox |☑️ |-|-| +|radiobutton |☑️ |-|-| +|combobox |☑️ |-|-| +|groupbox |☑️ |-|-| +|picturebox |☑️ |-|-| +|progressbar |☑️ |-|-| +|trackbar |☑️ |-|-| +|NumericUpDown |☑️ |-|-| + +Controls will maintain the `WinForm` usage habits as much as possible. + +Add `STNodeEditorCanvas.cs` + +To replace `STNodeEditor.cs` + +```cs +var canvas = new STNodeEditorCanvas("layer_name"); +canvas.Nodes.add(new STNode_1()); +canvas.Nodes.add(new STNode_2()); +STNodeEditor_1.Canvas = canvas; +// STNodeEditor.Layers.Add(canvas) This method is pending. +/* + The purpose of this is to achieve a TabControl-like effect, where the user may have multiple canvases to load. + So he had to add multiple STNodeEditors for switching. So use STNodeEditorCanvas to replace the original STNodeEditor. + And STNodeEditor only acts as a canvas container. +*/ +``` + +Add `STNodeSpy.cs` + +Although `STNodeEditor` can distinguish data types by option point color, it does not rule out that there will be different data types with the same color, especially if the node is not developed by one person. + +`STNodeSpy` is provided as a built-in node just like `STNodeHub`, it will be used in a similar way to `SPY++`. + + +Add `STNodeGroup.cs` + +The current `STNodeEditor` does not have the function of grouping. The author is going to try to use the same grouping method as `Blender`. After all, the author really likes `Blender`. Use the group as a node. + +```cs +var group = new STNodeGroup("group_name"); +group.Nodes.Add(new STNode_1()); +group.Nodes.Add(new STNode_2()); +var layer = new STNodeEditorLayer("layer_name"); +layder.Nodes.Add(group); +/* + The group has editable properties and it will exist in the layer as a normal node. But it can be expanded. + When the group is expanded, it will become an independent layer, and nodes can be added and removed. + You can refer to Blender's group, which can be understood as taking the initial input and final output of the entire canvas as the input and output of a node. +*/ +``` + +The above is for reference only, please refer to the actual product for the final effect. 😏😏😏😏😏 (after all, the author is a dead salted fish, and the possibility of strikes and false propaganda cannot be ruled out). + +If one of the above functions has been completed, a time will be marked next to it. For example: + +(2023-12-21)`Add high DPI support` + +If you have any ideas you can contact the author: 2212233137@qq.com \ No newline at end of file diff --git a/WinNodeEditorDemo/AttrTestNode.cs b/WinNodeEditorDemo/AttrTestNode.cs index b9c7fd2..8613c14 100644 --- a/WinNodeEditorDemo/AttrTestNode.cs +++ b/WinNodeEditorDemo/AttrTestNode.cs @@ -6,6 +6,7 @@ using ST.Library.UI.NodeEditor; using System.Drawing; using System.Windows.Forms; +using SkiaSharp; namespace WinNodeEditorDemo { @@ -87,9 +88,8 @@ protected override object GetValueFromString(string strText) { //绘制属性窗口值区域时候调用 protected override void OnDrawValueRectangle(DrawingTools dt) { base.OnDrawValueRectangle(dt);//先采用默认的绘制 并再绘制颜色预览 - dt.SolidBrush.Color = (Color)this.GetValue(null); - dt.Graphics.FillRectangle(dt.SolidBrush, m_rect);//填充颜色 - dt.Graphics.DrawRectangle(Pens.Black, m_rect); //绘制边框 + var color = SkiaDrawingHelper.ToSKColor((Color)this.GetValue(null)); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var fill = new SKPaint { Color = color, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var stroke = new SKPaint { Color = SKColors.Black, Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { canvas.DrawRect(m_rect.Left, m_rect.Top, m_rect.Width, m_rect.Height, fill); canvas.DrawRect(m_rect.Left, m_rect.Top, m_rect.Width, m_rect.Height, stroke);} }); } protected override void OnMouseClick(MouseEventArgs e) { diff --git a/WinNodeEditorDemo/Blender/FrmEnumSelect.cs b/WinNodeEditorDemo/Blender/FrmEnumSelect.cs index 3c8e87f..2e6abfd 100644 --- a/WinNodeEditorDemo/Blender/FrmEnumSelect.cs +++ b/WinNodeEditorDemo/Blender/FrmEnumSelect.cs @@ -1,26 +1,19 @@ -using System; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - using System.Drawing; using System.Windows.Forms; +using ST.Library.UI.NodeEditor; +using SkiaSharp; namespace WinNodeEditorDemo.Blender { - /// - /// 此类仅演示 作为MixRGB节点的下拉选择框弹出菜单 - /// public class FrmEnumSelect : Form { private Point m_pt; private int m_nWidth; private float m_scale; private List m_lst = new List(); - private StringFormat m_sf; - public Enum Enum { get; set; } - private bool m_bClosed; public FrmEnumSelect(Enum e, Point pt, int nWidth, float scale) { @@ -31,12 +24,9 @@ public FrmEnumSelect(Enum e, Point pt, int nWidth, float scale) { m_pt = pt; m_scale = scale; m_nWidth = nWidth; - m_sf = new StringFormat(); - m_sf.LineAlignment = StringAlignment.Center; - this.ShowInTaskbar = false; this.BackColor = Color.FromArgb(255, 34, 34, 34); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.FormBorderStyle = FormBorderStyle.None; } protected override void OnLoad(EventArgs e) { @@ -48,27 +38,19 @@ protected override void OnLoad(EventArgs e) { protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); - Graphics g = e.Graphics; - g.ScaleTransform(m_scale, m_scale); - Rectangle rect = new Rectangle(0, 0, this.Width, 20); - foreach (var v in m_lst) { - g.DrawString(v.ToString(), this.Font, Brushes.White, rect, m_sf); - rect.Y += rect.Height; - } } protected override void OnMouseClick(MouseEventArgs e) { base.OnMouseClick(e); int nIndex = e.Y / (int)(20 * m_scale); if (nIndex >= 0 && nIndex < m_lst.Count) this.Enum = (Enum)m_lst[nIndex]; - this.DialogResult = System.Windows.Forms.DialogResult.OK; + this.DialogResult = DialogResult.OK; m_bClosed = true; } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); if (m_bClosed) return; - //this.DialogResult = System.Windows.Forms.DialogResult.None; this.Close(); } } diff --git a/WinNodeEditorDemo/Blender/STNodeCheckBox.cs b/WinNodeEditorDemo/Blender/STNodeCheckBox.cs index db3bfeb..fdda2e7 100644 --- a/WinNodeEditorDemo/Blender/STNodeCheckBox.cs +++ b/WinNodeEditorDemo/Blender/STNodeCheckBox.cs @@ -5,6 +5,7 @@ using System.Drawing; using ST.Library.UI.NodeEditor; +using SkiaSharp; namespace WinNodeEditorDemo.Blender { @@ -35,14 +36,17 @@ protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) { } protected override void OnPaint(DrawingTools dt) { - //base.OnPaint(dt); - Graphics g = dt.Graphics; - g.FillRectangle(Brushes.Gray, 0, 5, 10, 10); - m_sf.Alignment = StringAlignment.Near; - g.DrawString(this.Text, this.Font, Brushes.LightGray, new Rectangle(15, 0, this.Width - 20, 20), m_sf); - if (this.Checked) { - g.FillRectangle(Brushes.Black, 2, 7, 6, 6); - } + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + using (var gray = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var black = new SKPaint { Color = SKColors.Black, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var textPaint = new SKPaint { Color = SKColors.LightGray, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + canvas.DrawRect(0, 5, 10, 10, gray); + var metrics = textPaint.FontMetrics; + float textY = (20 - (metrics.Descent - metrics.Ascent)) / 2 - metrics.Ascent; + canvas.DrawText(this.Text ?? string.Empty, 15, textY, textPaint); + if (this.Checked) canvas.DrawRect(2, 7, 6, 6, black); + } + }); } } } diff --git a/WinNodeEditorDemo/Blender/STNodeProgress.cs b/WinNodeEditorDemo/Blender/STNodeProgress.cs index 5fa5587..5d84a1e 100644 --- a/WinNodeEditorDemo/Blender/STNodeProgress.cs +++ b/WinNodeEditorDemo/Blender/STNodeProgress.cs @@ -4,6 +4,7 @@ using System.Text; using ST.Library.UI.NodeEditor; +using SkiaSharp; using System.Drawing; namespace WinNodeEditorDemo.Blender @@ -32,14 +33,21 @@ protected virtual void OnValueChanged(EventArgs e) { protected override void OnPaint(DrawingTools dt) { base.OnPaint(dt); - Graphics g = dt.Graphics; - g.FillRectangle(Brushes.Gray, this.ClientRectangle); - g.FillRectangle(Brushes.CornflowerBlue, 0, 0, (int)((float)this._Value / 100 * this.Width), this.Height); - m_sf.Alignment = StringAlignment.Near; - g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, m_sf); - m_sf.Alignment = StringAlignment.Far; - g.DrawString(((float)this._Value / 100).ToString("F2"), this.Font, Brushes.White, this.ClientRectangle, m_sf); - + float progress = (float)this._Value / 100; + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var fg = new SKPaint { Color = SKColors.CornflowerBlue, Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { + canvas.DrawRect(0, 0, this.Width, this.Height, bg); + canvas.DrawRect(0, 0, this.Width * progress, this.Height, fg); + var metrics = text.FontMetrics; + float y = (this.Height - (metrics.Descent - metrics.Ascent)) / 2 - metrics.Ascent; + canvas.DrawText(this.Text ?? string.Empty, 2, y, text); + var pct = progress.ToString("F2"); + float tw = text.MeasureText(pct); + canvas.DrawText(pct, this.Width - tw - 2, y, text); + } + }); } protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) { diff --git a/WinNodeEditorDemo/Blender/STNodeSelectBox.cs b/WinNodeEditorDemo/Blender/STNodeSelectBox.cs index 9b36e87..8b48743 100644 --- a/WinNodeEditorDemo/Blender/STNodeSelectBox.cs +++ b/WinNodeEditorDemo/Blender/STNodeSelectBox.cs @@ -5,6 +5,7 @@ using System.Drawing; using ST.Library.UI.NodeEditor; +using SkiaSharp; namespace WinNodeEditorDemo.Blender { @@ -28,15 +29,22 @@ protected virtual void OnValueChanged(EventArgs e) { } protected override void OnPaint(DrawingTools dt) { - Graphics g = dt.Graphics; - dt.SolidBrush.Color = Color.FromArgb(80, 0, 0, 0); - g.FillRectangle(dt.SolidBrush, this.ClientRectangle); - m_sf.Alignment = StringAlignment.Near; - g.DrawString(this.Enum.ToString(), this.Font, Brushes.White, this.ClientRectangle, m_sf); - g.FillPolygon(Brushes.Gray, new Point[]{ - new Point(this.Right - 25, 7), - new Point(this.Right - 15, 7), - new Point(this.Right - 20, 12) + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { + using (var bg = new SKPaint { Color = new SKColor(0, 0, 0, 80), Style = SKPaintStyle.Fill, IsAntialias = true }) + using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) + using (var arrow = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) { + canvas.DrawRect(0, 0, this.Width, this.Height, bg); + var metrics = text.FontMetrics; + float y = (this.Height - (metrics.Descent - metrics.Ascent)) / 2 - metrics.Ascent; + canvas.DrawText(this.Enum == null ? string.Empty : this.Enum.ToString(), 2, y, text); + using (var path = new SKPath()) { + path.MoveTo(this.Width - 25, 7); + path.LineTo(this.Width - 15, 7); + path.LineTo(this.Width - 20, 12); + path.Close(); + canvas.DrawPath(path, arrow); + } + } }); } diff --git a/WinNodeEditorDemo/CalcNode.cs b/WinNodeEditorDemo/CalcNode.cs index 01fe541..90c6377 100644 --- a/WinNodeEditorDemo/CalcNode.cs +++ b/WinNodeEditorDemo/CalcNode.cs @@ -5,6 +5,7 @@ using ST.Library.UI.NodeEditor; using System.Drawing; +using SkiaSharp; namespace WinNodeEditorDemo { @@ -33,8 +34,7 @@ protected override void OnCreate() { ctrl.Paint += (s, e) => { m_sf.Alignment = StringAlignment.Far; STNodeControl c = s as STNodeControl; - Graphics g = e.DrawingTools.Graphics; - g.DrawString("0", ctrl.Font, Brushes.White, c.ClientRectangle, m_sf); + SkiaDrawingHelper.RenderToCanvas(e.DrawingTools.Canvas, canvas => { using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, ctrl.Font.Size), IsAntialias = true }) { var fm = text.FontMetrics; float y = (c.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float w = text.MeasureText("0"); canvas.DrawText("0", c.Width - w - 2, y, text); } }); }; string[] strs = { //按钮文本 @@ -58,8 +58,7 @@ protected override void OnCreate() { if (i == 8) ctrl.Paint += (s, e) => { m_sf.Alignment = StringAlignment.Center; STNodeControl c = s as STNodeControl; - Graphics g = e.DrawingTools.Graphics; - g.DrawString("_", ctrl.Font, Brushes.White, c.ClientRectangle, m_sf); + SkiaDrawingHelper.RenderToCanvas(e.DrawingTools.Canvas, canvas => { using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, ctrl.Font.Size), IsAntialias = true }) { var fm = text.FontMetrics; float y = (c.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float w = text.MeasureText("_"); canvas.DrawText("_", (c.Width - w) / 2, y, text); } }); }; ctrl.MouseClick += (s, e) => System.Windows.Forms.MessageBox.Show(((STNodeControl)s).Text); } diff --git a/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs b/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs index 15fbbea..13ed67a 100644 --- a/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs +++ b/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs @@ -6,6 +6,7 @@ using ST.Library.UI.NodeEditor; using System.Drawing; using System.Drawing.Imaging; +using SkiaSharp; namespace WinNodeEditorDemo.ImageNode { @@ -78,10 +79,8 @@ void m_op_img_in_DataTransfer(object sender, STNodeOptionEventArgs e) { protected override void OnDrawBody(DrawingTools dt) { base.OnDrawBody(dt); - Graphics g = dt.Graphics; Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 120, 80); - g.FillRectangle(Brushes.Gray, rect); - if (m_img_draw != null) g.DrawImage(m_img_draw, rect); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) { canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); if (m_img_draw != null) { using (var bmp = SkiaDrawingHelper.ToSKBitmap(m_img_draw)) { if (bmp != null) canvas.DrawBitmap(bmp, new SKRect(rect.Left, rect.Top, rect.Right, rect.Bottom)); } } } }); } } } diff --git a/WinNodeEditorDemo/ImageNode/ImageInputNode.cs b/WinNodeEditorDemo/ImageNode/ImageInputNode.cs index be7fb01..2a8a441 100644 --- a/WinNodeEditorDemo/ImageNode/ImageInputNode.cs +++ b/WinNodeEditorDemo/ImageNode/ImageInputNode.cs @@ -6,6 +6,7 @@ using System.Drawing; using System.Windows.Forms; using System.Reflection; +using SkiaSharp; namespace WinNodeEditorDemo.ImageNode { @@ -37,10 +38,8 @@ protected override void OnCreate() { protected override void OnDrawBody(DrawingTools dt) { base.OnDrawBody(dt); - Graphics g = dt.Graphics; Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 140, 80); - g.FillRectangle(Brushes.Gray, rect); - if (m_img_draw != null) g.DrawImage(m_img_draw, rect); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) { canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); if (m_img_draw != null) { using (var bmp = SkiaDrawingHelper.ToSKBitmap(m_img_draw)) { if (bmp != null) canvas.DrawBitmap(bmp, new SKRect(rect.Left, rect.Top, rect.Right, rect.Bottom)); } } } }); } } /// @@ -77,8 +76,7 @@ protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) { protected override void OnDrawValueRectangle(DrawingTools dt) { base.OnDrawValueRectangle(dt); //在STNodePropertyGrid绘制此属性区域时候将"打开"按钮绘制上去 - dt.Graphics.FillRectangle(Brushes.Gray, m_rect_open); - dt.Graphics.DrawString("+", this.Control.Font, Brushes.White, m_rect_open, m_sf); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var tx = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Control.Font.Size), IsAntialias = true }) { canvas.DrawRect(m_rect_open.Left, m_rect_open.Top, m_rect_open.Width, m_rect_open.Height, bg); var fm = tx.FontMetrics; float y = m_rect_open.Top + (m_rect_open.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float x = m_rect_open.Left + (m_rect_open.Width - tx.MeasureText("+")) / 2; canvas.DrawText("+", x, y, tx);} }); } } } diff --git a/WinNodeEditorDemo/NumberNode/NumberAddNode.cs b/WinNodeEditorDemo/NumberNode/NumberAddNode.cs index 188be18..9023def 100644 --- a/WinNodeEditorDemo/NumberNode/NumberAddNode.cs +++ b/WinNodeEditorDemo/NumberNode/NumberAddNode.cs @@ -5,6 +5,7 @@ using ST.Library.UI.NodeEditor; using System.Drawing; +using SkiaSharp; namespace WinNodeEditorDemo.NumberNode { @@ -67,7 +68,7 @@ protected override void OnDrawOptionText(DrawingTools dt, STNodeOption op) { m_sf.Alignment = StringAlignment.Far; strText = (m_nNum1 + m_nNum2).ToString(); } - dt.Graphics.DrawString(strText, this.Font, Brushes.White, op.TextRectangle, m_sf); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { var fm = text.FontMetrics; float y = op.TextRectangle.Top + (op.TextRectangle.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float x = m_sf.Alignment == StringAlignment.Far ? op.TextRectangle.Right - text.MeasureText(strText) - 2 : op.TextRectangle.Left + 2; canvas.DrawText(strText, x, y, text); } }); } } } diff --git a/WinNodeEditorDemo/NumberNode/NumberInputNode.cs b/WinNodeEditorDemo/NumberNode/NumberInputNode.cs index 335babe..23a9f9d 100644 --- a/WinNodeEditorDemo/NumberNode/NumberInputNode.cs +++ b/WinNodeEditorDemo/NumberNode/NumberInputNode.cs @@ -5,6 +5,7 @@ using ST.Library.UI.NodeEditor; using System.Drawing; +using SkiaSharp; namespace WinNodeEditorDemo.NumberNode { @@ -46,7 +47,8 @@ protected override void OnCreate() { /// 需要绘制的选项 protected override void OnDrawOptionText(DrawingTools dt, STNodeOption op) { base.OnDrawOptionText(dt, op); - dt.Graphics.DrawString(this._Number.ToString(), this.Font, Brushes.White, op.TextRectangle, m_sf); + var txt = this._Number.ToString(); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { var fm = text.FontMetrics; float y = op.TextRectangle.Top + (op.TextRectangle.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float x = op.TextRectangle.Right - text.MeasureText(txt) - 2; canvas.DrawText(txt, x, y, text); } }); } } } diff --git a/WinNodeEditorDemo/Properties/Resources.Designer.cs b/WinNodeEditorDemo/Properties/Resources.Designer.cs index 307b927..f44d87f 100644 --- a/WinNodeEditorDemo/Properties/Resources.Designer.cs +++ b/WinNodeEditorDemo/Properties/Resources.Designer.cs @@ -1,55 +1,54 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ -namespace WinNodeEditorDemo.Properties -{ - - +namespace WinNodeEditorDemo.Properties { + using System; + + /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// 一个强类型的资源类,用于查找本地化的字符串等。 /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - + /// - /// Returns the cached ResourceManager instance used by this class. + /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { - if ((resourceMan == null)) { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinNodeEditorDemo.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { diff --git a/WinNodeEditorDemo/Properties/Settings.Designer.cs b/WinNodeEditorDemo/Properties/Settings.Designer.cs index 52bf6f2..6ef1c7d 100644 --- a/WinNodeEditorDemo/Properties/Settings.Designer.cs +++ b/WinNodeEditorDemo/Properties/Settings.Designer.cs @@ -1,24 +1,22 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ -namespace WinNodeEditorDemo.Properties -{ - - +namespace WinNodeEditorDemo.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - + public static Settings Default { get { return defaultInstance; diff --git a/WinNodeEditorDemo/ToolStripRendererEx.cs b/WinNodeEditorDemo/ToolStripRendererEx.cs index 64943d8..0a76663 100644 --- a/WinNodeEditorDemo/ToolStripRendererEx.cs +++ b/WinNodeEditorDemo/ToolStripRendererEx.cs @@ -4,6 +4,8 @@ using System.Text; using System.Windows.Forms; using System.Drawing.Drawing2D; +using ST.Library.UI.NodeEditor; +using SkiaSharp; namespace WinNodeEditorDemo { @@ -23,27 +25,15 @@ protected override void InitializeItem(ToolStripItem item) { } protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) { - using (SolidBrush sb = new SolidBrush(Color.FromArgb(34, 34, 34))) { - e.Graphics.FillRectangle(sb, e.AffectedBounds); - } base.OnRenderToolStripBackground(e); } protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) { - e.Graphics.DrawRectangle(Pens.Black, e.AffectedBounds.X, e.AffectedBounds.Y, e.AffectedBounds.Width - 1, e.AffectedBounds.Height - 1); base.OnRenderToolStripBorder(e); } - //protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) { - // using (SolidBrush sb = new SolidBrush(Color.FromArgb(50, 50, 50))) { - // e.Graphics.FillRectangle(sb, e.AffectedBounds); - // } - // base.OnRenderImageMargin(e); - //} - protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { e.TextColor = e.Item.Selected ? Color.White : Color.LightGray; - e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; e.TextRectangle = new Rectangle(e.TextRectangle.Left, e.TextRectangle.Top, e.TextRectangle.Width, 30); base.OnRenderItemText(e); } @@ -54,27 +44,12 @@ protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { } protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) { - Point ptEnd = new Point(e.Item.ContentRectangle.X + e.Item.Width / 2, e.Item.ContentRectangle.Y); - using (LinearGradientBrush lgb = new LinearGradientBrush(e.Item.ContentRectangle.Location, ptEnd, Color.Transparent, Color.Gray)) { - lgb.WrapMode = WrapMode.TileFlipX; - using (Pen p = new Pen(lgb)) { - e.Graphics.DrawLine(p, e.Item.ContentRectangle.Location, new Point(e.Item.ContentRectangle.Right, ptEnd.Y)); - } - } - //e.Graphics.DrawLine(Pens.Gray, e.Item.ContentRectangle.Location, new Point(e.Item.ContentRectangle.Right, ptEnd.Y)); base.OnRenderSeparator(e); } protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { - if (e.Item.Selected) - e.Graphics.FillRectangle(m_brush, e.Item.ContentRectangle); - else - base.OnRenderMenuItemBackground(e); + base.OnRenderMenuItemBackground(e); } - //protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { - // //base.OnRenderItemImage(e); - // e.Graphics.DrawImage(e.Image, e.ImageRectangle.X, e.ImageRectangle.Y, 17, 17); - //} } } diff --git a/WinNodeEditorDemo/WinNodeEditorDemo.csproj b/WinNodeEditorDemo/WinNodeEditorDemo.csproj index 6600de5..cd5bb2c 100644 --- a/WinNodeEditorDemo/WinNodeEditorDemo.csproj +++ b/WinNodeEditorDemo/WinNodeEditorDemo.csproj @@ -1,111 +1,16 @@ - - + - Debug - x86 - 8.0.30703 - 2.0 - {4E1829B5-2160-48F5-ABD6-11914A6A25DD} WinExe - Properties - WinNodeEditorDemo - WinNodeEditorDemo - v3.5 - 512 + net10.0-windows + true + disable + disable + false + preview - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - Form - - - - - - - - Form - - - Form1.cs - - - - - - - - - - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - + - - {EFFCC270-4999-4077-A543-56CCCCE92147} - ST.Library.UI - + + - - - - \ No newline at end of file + diff --git a/WinNodeEditorDemo/app.config b/WinNodeEditorDemo/app.config new file mode 100644 index 0000000..3e0e37c --- /dev/null +++ b/WinNodeEditorDemo/app.config @@ -0,0 +1,3 @@ + + + diff --git a/WinNodeEditorTest.sln b/WinNodeEditorTest.sln index aca14ba..38a33f2 100644 --- a/WinNodeEditorTest.sln +++ b/WinNodeEditorTest.sln @@ -1,10 +1,14 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.35906.104 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ST.Library.UI", "ST.Library.UI\ST.Library.UI.csproj", "{EFFCC270-4999-4077-A543-56CCCCE92147}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinNodeEditorDemo", "WinNodeEditorDemo\WinNodeEditorDemo.csproj", "{4E1829B5-2160-48F5-ABD6-11914A6A25DD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfNodeEdittorDemo", "WpfNodeEdittorDemo\WpfNodeEdittorDemo.csproj", "{B355CF99-7494-472A-8E0E-1543236FFC88}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,8 +39,23 @@ Global {4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|Mixed Platforms.Build.0 = Release|x86 {4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|x86.ActiveCfg = Release|x86 {4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|x86.Build.0 = Release|x86 + {B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|x86.ActiveCfg = Debug|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|x86.Build.0 = Debug|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Any CPU.Build.0 = Release|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Release|x86.ActiveCfg = Release|Any CPU + {B355CF99-7494-472A-8E0E-1543236FFC88}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CE29E4E7-E2FB-4387-9743-04332BC01645} + EndGlobalSection EndGlobal diff --git a/WpfNodeEdittorDemo/App.xaml b/WpfNodeEdittorDemo/App.xaml new file mode 100644 index 0000000..90ef0c3 --- /dev/null +++ b/WpfNodeEdittorDemo/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/WpfNodeEdittorDemo/App.xaml.cs b/WpfNodeEdittorDemo/App.xaml.cs new file mode 100644 index 0000000..d7e575d --- /dev/null +++ b/WpfNodeEdittorDemo/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace WpfNodeEdittorDemo +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/WpfNodeEdittorDemo/AssemblyInfo.cs b/WpfNodeEdittorDemo/AssemblyInfo.cs new file mode 100644 index 0000000..a8dc727 --- /dev/null +++ b/WpfNodeEdittorDemo/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/WpfNodeEdittorDemo/AttrTestNode.cs b/WpfNodeEdittorDemo/AttrTestNode.cs new file mode 100644 index 0000000..b9b9737 --- /dev/null +++ b/WpfNodeEdittorDemo/AttrTestNode.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; +using SkiaSharp; +using System.Windows.Forms; + +namespace WinNodeEditorDemo +{ + [STNode("/", "Crystal_lz", "2212233137@qq.com", "www.st233.com", "关于此节点的描述信息\r\n此类为\r\nSTNodeAttribute\r\nSTNodePropertyAttribute\r\n效果演示类")] + public class AttrTestNode : STNode + { + //因为属性编辑器默认并不支持Color类型数据 所以这里重写一个描述器并指定 + [STNodeProperty("颜色", "颜色信息", DescriptorType = typeof(DescriptorForColor))] + public Color Color { get; set; } + + [STNodeProperty("整型数组", "整型数组测试")] + public int[] IntArr { get; set; } + + [STNodeProperty("布尔", "布尔类型测试")] + public bool Bool { get; set; } + + [STNodeProperty("字符串", "字符串类型测试")] + public string String { get; set; } + + [STNodeProperty("整型", "整型测试")] + public int Int { get; set; } + + [STNodeProperty("浮点数", "浮点数类型测试")] + public float Float { get; set; } + + [STNodeProperty("枚举值", "枚举类型测试 -> FormBorderStyle")] + public FormBorderStyle STYLE { get; set; } + + public AttrTestNode() { + this.String = "string"; + IntArr = new int[] { 10, 20 }; + base.InputOptions.Add("string", typeof(string), false); + base.OutputOptions.Add("string", typeof(string), false); + this.Title = "AttrTestNode"; + this.TitleColor = Color.FromArgb(200, Color.Goldenrod); + } + /// + /// 此方法为魔术方法(Magic function) + /// 若存在 static void ShowHelpInfo(string) 且此类被STNodeAttribute标记 + /// 则此方法将作为属性编辑器上 查看帮助 功能 + /// + /// 此类所在的模块所在的文件路径 + public static void ShowHelpInfo(string strFileName) { + MessageBox.Show("this is -> ShowHelpInfo(string);\r\n" + strFileName); + } + + protected override void OnOwnerChanged() { + base.OnOwnerChanged(); + if (this.Owner == null) return; + this.Owner.SetTypeColor(typeof(string), Color.Goldenrod); + } + } + /// + /// 因为属性编辑器默认并不支持Color类型数据 所以这里重写一个描述器 + /// + public class DescriptorForColor : STNodePropertyDescriptor + { + private Rectangle m_rect;//此区域用作 属性窗口上绘制颜色预览 + //当此属性在属性窗口中被确定位置时候发生 + protected override void OnSetItemLocation() { + base.OnSetItemLocation(); + Rectangle rect = base.RectangleR; + m_rect = new Rectangle(rect.Right - 25, rect.Top + 5, 19, 12); + } + //将属性值转换为字符串 属性窗口值绘制时将采用此字符串 + protected override string GetStringFromValue() { + Color clr = (Color)this.GetValue(null); + return clr.A + "," + clr.R + "," + clr.G + "," + clr.B; + } + //将属性窗口中输入的字符串转化为Color属性 当属性窗口中用户确认输入时调用 + protected override object GetValueFromString(string strText) { + string[] strClr = strText.Split(','); + return Color.FromArgb( + int.Parse(strClr[0]), //A + int.Parse(strClr[1]), //R + int.Parse(strClr[2]), //G + int.Parse(strClr[3])); //B + } + //绘制属性窗口值区域时候调用 + protected override void OnDrawValueRectangle(DrawingTools dt) { + base.OnDrawValueRectangle(dt);//先采用默认的绘制 并再绘制颜色预览 + var color = SkiaDrawingHelper.ToSKColor((Color)this.GetValue(null)); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var fill = new SKPaint { Color = color, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var stroke = new SKPaint { Color = SKColors.Black, Style = SKPaintStyle.Stroke, StrokeWidth = 1, IsAntialias = true }) { canvas.DrawRect(m_rect.Left, m_rect.Top, m_rect.Width, m_rect.Height, fill); canvas.DrawRect(m_rect.Left, m_rect.Top, m_rect.Width, m_rect.Height, stroke);} }); + } + + protected override void OnMouseClick(MouseEventArgs e) { + //如果用户点击在 颜色预览区域 则弹出系统颜色对话框 + if (m_rect.Contains(e.Location)) { + ColorDialog cd = new ColorDialog(); + if (cd.ShowDialog() != DialogResult.OK) return; + this.SetValue(cd.Color, null); + this.Invalidate(); + return; + } + //否则其他区域将采用默认处理方式 弹出字符串输入框 + base.OnMouseClick(e); + } + } +} diff --git a/WpfNodeEdittorDemo/Blender/BlenderMixColorNode.cs b/WpfNodeEdittorDemo/Blender/BlenderMixColorNode.cs new file mode 100644 index 0000000..8de3399 --- /dev/null +++ b/WpfNodeEdittorDemo/Blender/BlenderMixColorNode.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅仅是演示 并不包含颜色混合功能 + /// + [STNode("/Blender/", "Crystal_lz", "2212233137@qq.com", "st233.com", "this is blender mixrgb node")] + public class BlenderMixColorNode : STNode + { + private ColorMixType _MixType; + [STNodeProperty("MixType","This is MixType")] + public ColorMixType MixType { + get { return _MixType; } + set { + _MixType = value; + m_ctrl_select.Enum = value; //当属性被赋值后 对应控件状态值也应当被修改 + } + } + + private bool _Clamp; + [STNodeProperty("Clamp","This is Clamp")] + public bool Clamp { + get { return _Clamp; } + set { _Clamp = value; m_ctrl_checkbox.Checked = value; } + } + + private int _Fac = 50; + [STNodeProperty("Fac", "This is Fac")] + public int Fac { + get { return _Fac; } + set { + if (value < 0) value = 0; + if (value > 100) value = 100; + _Fac = value; m_ctrl_progess.Value = value; + } + } + + private Color _Color1 = Color.LightGray;//默认的DescriptorType不支持颜色的显示 需要扩展 + [STNodeProperty("Color1", "This is color1", DescriptorType = typeof(WinNodeEditorDemo.DescriptorForColor))] + public Color Color1 { + get { return _Color1; } + set { _Color1 = value; m_ctrl_btn_1.BackColor = value; } + } + + private Color _Color2 = Color.LightGray; + [STNodeProperty("Color2", "This is color2", DescriptorType = typeof(WinNodeEditorDemo.DescriptorForColor))] + public Color Color2 { + get { return _Color2; } + set { _Color2 = value; m_ctrl_btn_2.BackColor = value; } + } + + public enum ColorMixType { + Mix, + Value, + Color, + Hue, + Add, + Subtract + } + + private STNodeSelectEnumBox m_ctrl_select; //自定义控件 + private STNodeProgress m_ctrl_progess; + private STNodeCheckBox m_ctrl_checkbox; + private STNodeColorButton m_ctrl_btn_1; + private STNodeColorButton m_ctrl_btn_2; + + protected override void OnCreate() { + base.OnCreate(); + this.TitleColor = Color.FromArgb(200, Color.DarkKhaki); + this.Title = "MixRGB"; + this.AutoSize = false; + this.Size = new Size(140, 142); + + this.OutputOptions.Add("Color", typeof(Color), true); + + this.InputOptions.Add(STNodeOption.Empty); //空白节点 仅站位 不参与绘制与事件触发 + this.InputOptions.Add(STNodeOption.Empty); + this.InputOptions.Add(STNodeOption.Empty); + this.InputOptions.Add("", typeof(float), true); + this.InputOptions.Add("Color1", typeof(Color), true); + this.InputOptions.Add("Color2", typeof(Color), true); + + m_ctrl_progess = new STNodeProgress(); //创建控件并添加到节点中 + m_ctrl_progess.Text = "Fac"; + m_ctrl_progess.DisplayRectangle = new Rectangle(10, 61, 120, 18); + m_ctrl_progess.ValueChanged += (s, e) => this._Fac = m_ctrl_progess.Value; + this.Controls.Add(m_ctrl_progess); + + m_ctrl_checkbox = new STNodeCheckBox(); + m_ctrl_checkbox.Text = "Clamp"; + m_ctrl_checkbox.DisplayRectangle = new Rectangle(10, 40, 120, 20); + m_ctrl_checkbox.ValueChanged += (s, e) => this._Clamp = m_ctrl_checkbox.Checked; + this.Controls.Add(m_ctrl_checkbox); + + m_ctrl_btn_1 = new STNodeColorButton(); + m_ctrl_btn_1.Text = ""; + m_ctrl_btn_1.BackColor = this._Color1; + m_ctrl_btn_1.DisplayRectangle = new Rectangle(80, 82, 50, 16); + m_ctrl_btn_1.ValueChanged += (s, e) => this._Color1 = m_ctrl_btn_1.BackColor; + this.Controls.Add(m_ctrl_btn_1); + + m_ctrl_btn_2 = new STNodeColorButton(); + m_ctrl_btn_2.Text = ""; + m_ctrl_btn_2.BackColor = this._Color2; + m_ctrl_btn_2.DisplayRectangle = new Rectangle(80, 102, 50, 16); + m_ctrl_btn_2.ValueChanged += (s, e) => this._Color2 = m_ctrl_btn_2.BackColor; + this.Controls.Add(m_ctrl_btn_2); + + m_ctrl_select = new STNodeSelectEnumBox(); + m_ctrl_select.DisplayRectangle = new Rectangle(10, 21, 120, 18); + m_ctrl_select.Enum = this._MixType; + m_ctrl_select.ValueChanged += (s, e) => this._MixType = (ColorMixType)m_ctrl_select.Enum; + this.Controls.Add(m_ctrl_select); + } + + protected override void OnOwnerChanged() { //当控件被添加时候 向编辑器提交自己的数据类型希望显示的颜色 + base.OnOwnerChanged(); + if (this.Owner == null) return; + this.Owner.SetTypeColor(typeof(float), Color.Gray); + this.Owner.SetTypeColor(typeof(Color), Color.Yellow); + } + } +} diff --git a/WpfNodeEdittorDemo/Blender/FrmEnumSelect.cs b/WpfNodeEdittorDemo/Blender/FrmEnumSelect.cs new file mode 100644 index 0000000..87a4ac9 --- /dev/null +++ b/WpfNodeEdittorDemo/Blender/FrmEnumSelect.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using SkiaSharp; +using System.Windows.Forms; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的下拉选择框弹出菜单 + /// + public class FrmEnumSelect : Form + { + private Point m_pt; + private int m_nWidth; + private float m_scale; + private List m_lst = new List(); + private StringFormat m_sf; + + public Enum Enum { get; set; } + + private bool m_bClosed; + + public FrmEnumSelect(Enum e, Point pt, int nWidth, float scale) { + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + foreach (var v in Enum.GetValues(e.GetType())) m_lst.Add(v); + this.Enum = e; + m_pt = pt; + m_scale = scale; + m_nWidth = nWidth; + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + + this.ShowInTaskbar = false; + this.BackColor = Color.FromArgb(255, 34, 34, 34); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + } + + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + this.Location = m_pt; + this.Width = (int)(m_nWidth * m_scale); + this.Height = (int)(m_lst.Count * 20 * m_scale); + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + } + + protected override void OnMouseClick(MouseEventArgs e) { + base.OnMouseClick(e); + int nIndex = e.Y / (int)(20 * m_scale); + if (nIndex >= 0 && nIndex < m_lst.Count) this.Enum = (Enum)m_lst[nIndex]; + this.DialogResult = System.Windows.Forms.DialogResult.OK; + m_bClosed = true; + } + + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + if (m_bClosed) return; + //this.DialogResult = System.Windows.Forms.DialogResult.None; + this.Close(); + } + } +} diff --git a/WpfNodeEdittorDemo/Blender/STNodeCheckBox.cs b/WpfNodeEdittorDemo/Blender/STNodeCheckBox.cs new file mode 100644 index 0000000..c7bf278 --- /dev/null +++ b/WpfNodeEdittorDemo/Blender/STNodeCheckBox.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using SkiaSharp; +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的复选框控件 + /// + public class STNodeCheckBox : STNodeControl + { + private bool _Checked; + + public bool Checked { + get { return _Checked; } + set { + _Checked = value; + this.Invalidate(); + } + } + + public event EventHandler ValueChanged; + protected virtual void OnValueChanged(EventArgs e) { + if (this.ValueChanged != null) this.ValueChanged(this, e); + } + + protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) { + base.OnMouseClick(e); + this.Checked = !this.Checked; + this.OnValueChanged(new EventArgs()); + } + + protected override void OnPaint(DrawingTools dt) { + //base.OnPaint(dt); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var gray = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var black = new SKPaint { Color = SKColors.Black, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var textPaint = new SKPaint { Color = SKColors.LightGray, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { canvas.DrawRect(0,5,10,10,gray); var fm=textPaint.FontMetrics; float y=(20-(fm.Descent-fm.Ascent))/2-fm.Ascent; canvas.DrawText(this.Text ?? string.Empty,15,y,textPaint); if(this.Checked) canvas.DrawRect(2,7,6,6,black);} }); + } + } +} diff --git a/WpfNodeEdittorDemo/Blender/STNodeColorButton.cs b/WpfNodeEdittorDemo/Blender/STNodeColorButton.cs new file mode 100644 index 0000000..1d7aa70 --- /dev/null +++ b/WpfNodeEdittorDemo/Blender/STNodeColorButton.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Windows.Forms; +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的颜色选择按钮 + /// + public class STNodeColorButton : STNodeControl + { + public event EventHandler ValueChanged; + protected virtual void OnValueChanged(EventArgs e) { + if (this.ValueChanged != null) this.ValueChanged(this, e); + } + + protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) { + base.OnMouseClick(e); + ColorDialog cd = new ColorDialog(); + if (cd.ShowDialog() != DialogResult.OK) return; + //this._Color = cd.Color; + this.BackColor = cd.Color; + this.OnValueChanged(new EventArgs()); + } + } +} diff --git a/WpfNodeEdittorDemo/Blender/STNodeProgress.cs b/WpfNodeEdittorDemo/Blender/STNodeProgress.cs new file mode 100644 index 0000000..fa917ff --- /dev/null +++ b/WpfNodeEdittorDemo/Blender/STNodeProgress.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; +using SkiaSharp; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的进度条控件 + /// + public class STNodeProgress : STNodeControl + { + private int _Value = 50; + + public int Value { + get { return _Value; } + set { + _Value = value; + this.Invalidate(); + } + } + + private bool m_bMouseDown; + + public event EventHandler ValueChanged; + protected virtual void OnValueChanged(EventArgs e) { + if (this.ValueChanged != null) this.ValueChanged(this, e); + } + + protected override void OnPaint(DrawingTools dt) { + base.OnPaint(dt); + float progress=(float)this._Value/100; SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var fg = new SKPaint { Color = SKColors.CornflowerBlue, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) { canvas.DrawRect(0,0,this.Width,this.Height,bg); canvas.DrawRect(0,0,this.Width*progress,this.Height,fg); var fm=text.FontMetrics; float y=(this.Height-(fm.Descent-fm.Ascent))/2-fm.Ascent; canvas.DrawText(this.Text ?? string.Empty,2,y,text); var pct=progress.ToString("F2"); canvas.DrawText(pct,this.Width-text.MeasureText(pct)-2,y,text);} }); + + } + + protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) { + base.OnMouseDown(e); + m_bMouseDown = true; + } + + protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) { + base.OnMouseUp(e); + m_bMouseDown = false; + } + + protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) { + base.OnMouseMove(e); + if (!m_bMouseDown) return; + int v = (int)((float)e.X / this.Width * 100); + if (v < 0) v = 0; + if (v > 100) v = 100; + this._Value = v; + this.OnValueChanged(new EventArgs()); + this.Invalidate(); + } + } +} diff --git a/WpfNodeEdittorDemo/Blender/STNodeSelectBox.cs b/WpfNodeEdittorDemo/Blender/STNodeSelectBox.cs new file mode 100644 index 0000000..5111567 --- /dev/null +++ b/WpfNodeEdittorDemo/Blender/STNodeSelectBox.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using SkiaSharp; +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的下拉框控件 + /// + public class STNodeSelectEnumBox : STNodeControl + { + private Enum _Enum; + public Enum Enum { + get { return _Enum; } + set { + _Enum = value; + this.Invalidate(); + } + } + + public event EventHandler ValueChanged; + protected virtual void OnValueChanged(EventArgs e) { + if (this.ValueChanged != null) this.ValueChanged(this, e); + } + + protected override void OnPaint(DrawingTools dt) { + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = new SKColor(0,0,0,80), Style = SKPaintStyle.Fill, IsAntialias = true }) using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Font.Size), IsAntialias = true }) using (var arrow = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) { canvas.DrawRect(0,0,this.Width,this.Height,bg); var fm=text.FontMetrics; float y=(this.Height-(fm.Descent-fm.Ascent))/2-fm.Ascent; canvas.DrawText(this.Enum == null ? string.Empty : this.Enum.ToString(),2,y,text); using (var path = new SKPath()) { path.MoveTo(this.Width-25,7); path.LineTo(this.Width-15,7); path.LineTo(this.Width-20,12); path.Close(); canvas.DrawPath(path,arrow);} } }); + } + + protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) { + base.OnMouseClick(e); + Point pt = new Point(this.Left + this.Owner.Left, this.Top + this.Owner.Top + this.Owner.TitleHeight); + pt = this.Owner.Owner.CanvasToControl(pt); + pt = this.Owner.Owner.PointToScreen(pt); + FrmEnumSelect frm = new FrmEnumSelect(this.Enum, pt, this.Width, this.Owner.Owner.CanvasScale); + var v = frm.ShowDialog(); + if (v != System.Windows.Forms.DialogResult.OK) return; + this.Enum = frm.Enum; + this.OnValueChanged(new EventArgs()); + } + } +} diff --git a/WpfNodeEdittorDemo/CalcNode.cs b/WpfNodeEdittorDemo/CalcNode.cs new file mode 100644 index 0000000..90c6377 --- /dev/null +++ b/WpfNodeEdittorDemo/CalcNode.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; +using SkiaSharp; + +namespace WinNodeEditorDemo +{ + /// + /// 此节点仅演示UI自定义以及控件 并不包含功能 + /// + [STNode("/", "DebugST", "2212233137@qq.com", "st233.com", "此节点仅演示UI自定义以及控件,并不包含功能.")] + public class CalcNode : STNode + { + private StringFormat m_f; + + protected override void OnCreate() { + base.OnCreate(); + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + this.Title = "Calculator"; + this.AutoSize = false; //注意需要先设置AutoSize=false 才能够进行大小设置 + this.Size = new Size(218, 308); + + var ctrl = new STNodeControl(); + ctrl.Text = ""; //此控件为显示屏幕 + ctrl.Location = new Point(13, 31); + ctrl.Size = new Size(190, 50); + this.Controls.Add(ctrl); + + ctrl.Paint += (s, e) => { + m_sf.Alignment = StringAlignment.Far; + STNodeControl c = s as STNodeControl; + SkiaDrawingHelper.RenderToCanvas(e.DrawingTools.Canvas, canvas => { using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, ctrl.Font.Size), IsAntialias = true }) { var fm = text.FontMetrics; float y = (c.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float w = text.MeasureText("0"); canvas.DrawText("0", c.Width - w - 2, y, text); } }); + }; + + string[] strs = { //按钮文本 + "MC", "MR", "MS", "M+", + "M-", "←", "CE", "C", "+", "√", + "7", "8", "9", "/", "%", + "4", "5", "6", "*", "1/x", + "1", "2", "3", "-", "=", + "0", " ", ".", "+" }; + Point p = new Point(13, 86); + for (int i = 0; i < strs.Length; i++) { + if (strs[i] == " ") continue; + ctrl = new STNodeControl(); + ctrl.Text = strs[i]; + ctrl.Size = new Size(34, 27); + ctrl.Left = 13 + (i % 5) * 39; + ctrl.Top = 86 + (i / 5) * 32; + if (ctrl.Text == "=") ctrl.Height = 59; + if (ctrl.Text == "0") ctrl.Width = 73; + this.Controls.Add(ctrl); + if (i == 8) ctrl.Paint += (s, e) => { + m_sf.Alignment = StringAlignment.Center; + STNodeControl c = s as STNodeControl; + SkiaDrawingHelper.RenderToCanvas(e.DrawingTools.Canvas, canvas => { using (var text = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, ctrl.Font.Size), IsAntialias = true }) { var fm = text.FontMetrics; float y = (c.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float w = text.MeasureText("_"); canvas.DrawText("_", (c.Width - w) / 2, y, text); } }); + }; + ctrl.MouseClick += (s, e) => System.Windows.Forms.MessageBox.Show(((STNodeControl)s).Text); + } + + this.OutputOptions.Add("Result", typeof(int), false); + } + } +} diff --git a/WpfNodeEdittorDemo/EmptyOptionTestNode.cs b/WpfNodeEdittorDemo/EmptyOptionTestNode.cs new file mode 100644 index 0000000..1c0449e --- /dev/null +++ b/WpfNodeEdittorDemo/EmptyOptionTestNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo +{ + [STNode("/")] + public class EmptyOptionTestNode : STNode + { + protected override void OnCreate() { + base.OnCreate(); + this.Title = "EmptyTest"; + this.InputOptions.Add(STNodeOption.Empty); + this.InputOptions.Add("string", typeof(string), false); + } + } +} diff --git a/WpfNodeEdittorDemo/ImageNode/ImageBaseNode.cs b/WpfNodeEdittorDemo/ImageNode/ImageBaseNode.cs new file mode 100644 index 0000000..54010d1 --- /dev/null +++ b/WpfNodeEdittorDemo/ImageNode/ImageBaseNode.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.ImageNode +{ + /// + /// 图片节点基类 用于确定节点风格 标题颜色 以及 数据类型颜色 + /// + public abstract class ImageBaseNode : STNode + { + /// + /// 需要作为显示绘制的图片 + /// + protected Image m_img_draw; + /// + /// 输出节点 + /// + protected STNodeOption m_op_img_out; + + protected override void OnCreate() { + base.OnCreate(); + m_op_img_out = this.OutputOptions.Add("", typeof(Image), false); + this.AutoSize = false; //此节点需要定制UI 所以无需AutoSize + //this.Size = new Size(320,240); + this.Width = 160; //手动设置节点大小 + this.Height = 120; + this.TitleColor = Color.FromArgb(200, Color.DarkCyan); + } + + protected override void OnOwnerChanged() { //向编辑器提交数据类型颜色 + base.OnOwnerChanged(); + if (this.Owner == null) return; + this.Owner.SetTypeColor(typeof(Image), Color.DarkCyan); + } + } +} diff --git a/WpfNodeEdittorDemo/ImageNode/ImageChannelNode.cs b/WpfNodeEdittorDemo/ImageNode/ImageChannelNode.cs new file mode 100644 index 0000000..97caf8e --- /dev/null +++ b/WpfNodeEdittorDemo/ImageNode/ImageChannelNode.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; +using SkiaSharp; +using System.Drawing.Imaging; + +namespace WinNodeEditorDemo.ImageNode +{ + [STNode("/Image")] + public class ImageChannelNode : ImageBaseNode + { + private STNodeOption m_op_img_in; //输入的节点 + private STNodeOption m_op_img_r; //R图 输出节点 + private STNodeOption m_op_img_g; //G图 输出节点 + private STNodeOption m_op_img_b; //B图 输出节点 + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "ImageChannel"; + + m_op_img_in = this.InputOptions.Add("", typeof(Image), true); + m_op_img_r = this.OutputOptions.Add("R", typeof(Image), false); + m_op_img_g = this.OutputOptions.Add("G", typeof(Image), false); + m_op_img_b = this.OutputOptions.Add("B", typeof(Image), false); + //当输入节点有数据输入时候 + m_op_img_in.DataTransfer += new STNodeOptionEventHandler(m_op_img_in_DataTransfer); + } + + void m_op_img_in_DataTransfer(object sender, STNodeOptionEventArgs e) { + //如果当前不是连接状态 或者 接受到的数据为空 + if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) { + m_op_img_out.TransferData(null); //向所有输出节点输出空数据 + m_op_img_r.TransferData(null); + m_op_img_g.TransferData(null); + m_op_img_b.TransferData(null); + m_img_draw = null; //需要绘制显示的图片置为空 + } else { + Bitmap bmp = (Bitmap)e.TargetOption.Data; //否则计算图片的RGB图像 + Bitmap bmp_r = new Bitmap(bmp.Width, bmp.Height); + Bitmap bmp_g = new Bitmap(bmp.Width, bmp.Height); + Bitmap bmp_b = new Bitmap(bmp.Width, bmp.Height); + BitmapData bmpData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData bmpData_r = bmp_r.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + BitmapData bmpData_g = bmp_g.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + BitmapData bmpData_b = bmp_b.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + byte[] byColor = new byte[bmpData.Height * bmpData.Stride]; + byte[] byColor_r = new byte[byColor.Length]; + byte[] byColor_g = new byte[byColor.Length]; + byte[] byColor_b = new byte[byColor.Length]; + System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, byColor, 0, byColor.Length); + for (int y = 0; y < bmpData.Height; y++) { + int ny = y * bmpData.Stride; + for (int x = 0; x < bmpData.Width; x++) { + int nx = x << 2; + byColor_b[ny + nx] = byColor[ny + nx]; + byColor_g[ny + nx + 1] = byColor[ny + nx + 1]; + byColor_r[ny + nx + 2] = byColor[ny + nx + 2]; + byColor_r[ny + nx + 3] = byColor_g[ny + nx + 3] = byColor_b[ny + nx + 3] = byColor[ny + nx + 3]; + } + } + bmp.UnlockBits(bmpData); + System.Runtime.InteropServices.Marshal.Copy(byColor_r, 0, bmpData_r.Scan0, byColor_r.Length); + System.Runtime.InteropServices.Marshal.Copy(byColor_g, 0, bmpData_g.Scan0, byColor_g.Length); + System.Runtime.InteropServices.Marshal.Copy(byColor_b, 0, bmpData_b.Scan0, byColor_b.Length); + bmp_r.UnlockBits(bmpData_r); + bmp_g.UnlockBits(bmpData_g); + bmp_b.UnlockBits(bmpData_b); + m_op_img_out.TransferData(bmp); //out选项 输出原图 + m_op_img_r.TransferData(bmp_r); //R选项输出R图 + m_op_img_g.TransferData(bmp_g); + m_op_img_b.TransferData(bmp_b); + m_img_draw = bmp; //需要绘制显示的图片 + } + } + + protected override void OnDrawBody(DrawingTools dt) { + base.OnDrawBody(dt); + Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 120, 80); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) { canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); if (m_img_draw != null) { using (var bmp = SkiaDrawingHelper.ToSKBitmap(m_img_draw)) { if (bmp != null) canvas.DrawBitmap(bmp, new SKRect(rect.Left, rect.Top, rect.Right, rect.Bottom)); } } } }); + } + } +} diff --git a/WpfNodeEdittorDemo/ImageNode/ImageInputNode.cs b/WpfNodeEdittorDemo/ImageNode/ImageInputNode.cs new file mode 100644 index 0000000..f75edaf --- /dev/null +++ b/WpfNodeEdittorDemo/ImageNode/ImageInputNode.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ST.Library.UI.NodeEditor; +using System.Drawing; +using SkiaSharp; +using System.Windows.Forms; +using System.Reflection; + +namespace WinNodeEditorDemo.ImageNode +{ + + [STNode("Image", "Crystal_lz", "2212233137@qq.com", "st233.com", "Image Node")] + public class ImageInputNode : ImageBaseNode + { + private string _FileName;//默认的DescriptorType不支持文件路径的选择 所以需要扩展 + [STNodeProperty("InputImage", "Click to select a image", DescriptorType = typeof(OpenFileDescriptor))] + public string FileName { + get { return _FileName; } + set { + Image img = null; //当文件名被设置时 加载图片并 向输出节点输出 + if (!string.IsNullOrEmpty(value)) { + img = Image.FromFile(value); + } + if (m_img_draw != null) m_img_draw.Dispose(); + m_img_draw = img; + _FileName = value; + m_op_img_out.TransferData(m_img_draw, true); + this.Invalidate(); + } + } + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "ImageInput"; + } + + protected override void OnDrawBody(DrawingTools dt) { + base.OnDrawBody(dt); + Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 140, 80); + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) { canvas.DrawRect(rect.Left, rect.Top, rect.Width, rect.Height, bg); if (m_img_draw != null) { using (var bmp = SkiaDrawingHelper.ToSKBitmap(m_img_draw)) { if (bmp != null) canvas.DrawBitmap(bmp, new SKRect(rect.Left, rect.Top, rect.Right, rect.Bottom)); } } } }); + } + } + /// + /// 对默认Descriptor进行扩展 使得支持文件路径选择 + /// + public class OpenFileDescriptor : STNodePropertyDescriptor + { + private Rectangle m_rect_open; //需要绘制"打开"按钮的区域 + private StringFormat m_sf; + + public OpenFileDescriptor() { + m_sf = new StringFormat(); + m_sf.Alignment = StringAlignment.Center; + m_sf.LineAlignment = StringAlignment.Center; + } + + protected override void OnSetItemLocation() { //当在STNodePropertyGrid上确定此属性需要显示的区域时候 + base.OnSetItemLocation(); //计算出"打开"按钮需要绘制的区域 + m_rect_open = new Rectangle( + this.RectangleR.Right - 20, + this.RectangleR.Top, + 20, + this.RectangleR.Height); + } + + protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) { + if (m_rect_open.Contains(e.Location)) { //点击在"打开"区域 则弹出文件选择框 + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Filter = "*.jpg|*.jpg|*.png|*.png"; + if (ofd.ShowDialog() != DialogResult.OK) return; + this.SetValue(ofd.FileName); + } else base.OnMouseClick(e); //否则默认处理方式 弹出文本输入框 + } + + protected override void OnDrawValueRectangle(DrawingTools dt) { + base.OnDrawValueRectangle(dt); //在STNodePropertyGrid绘制此属性区域时候将"打开"按钮绘制上去 + SkiaDrawingHelper.RenderToCanvas(dt.Canvas, canvas => { using (var bg = new SKPaint { Color = SKColors.Gray, Style = SKPaintStyle.Fill, IsAntialias = true }) using (var tx = new SKPaint { Color = SKColors.White, TextSize = Math.Max(10f, this.Control.Font.Size), IsAntialias = true }) { canvas.DrawRect(m_rect_open.Left, m_rect_open.Top, m_rect_open.Width, m_rect_open.Height, bg); var fm = tx.FontMetrics; float y = m_rect_open.Top + (m_rect_open.Height - (fm.Descent - fm.Ascent)) / 2 - fm.Ascent; float x = m_rect_open.Left + (m_rect_open.Width - tx.MeasureText("+")) / 2; canvas.DrawText("+", x, y, tx);} }); + } + } +} diff --git a/WpfNodeEdittorDemo/MainWindow.xaml b/WpfNodeEdittorDemo/MainWindow.xaml new file mode 100644 index 0000000..8f3d94c --- /dev/null +++ b/WpfNodeEdittorDemo/MainWindow.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + +