Imported ImNodes

This commit is contained in:
LuChiChick 2025-11-11 15:09:35 +08:00
parent 75eb1fc143
commit 97ec4ea413
25 changed files with 6879 additions and 5 deletions

View File

@ -6,7 +6,8 @@
"${workspaceFolder}/**",
"${workspaceFolder}/Inc",
"${workspaceFolder}/Lib/imgui-1.92.4-docking",
"${workspaceFolder}/Lib/imgui-1.92.4-docking/backends"
"${workspaceFolder}/Lib/imgui-1.92.4-docking/backends",
"${workspaceFolder}/Lib/imnodes-master-b2ec254"
],
"defines": [
"UNICODE",

View File

@ -2,6 +2,7 @@
#define __UI_LAYOUT__
#include "imgui.h"
#include "imnodes.h"
// UI布局
void UI_Layout();

View File

@ -0,0 +1,39 @@
---
AccessModifierOffset: -4
AlignAfterOpenBracket: 'AlwaysBreak'
AlignConsecutiveDeclarations: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'false'
AllowShortFunctionsOnASingleLine: 'true'
AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'true'
BinPackArguments: 'false'
BinPackParameters: 'false'
BraceWrapping: {
AfterCaseLabel: 'true'
AfterClass: 'true'
AfterControlStatement: 'true'
AfterEnum: 'true'
AfterFunction: 'true'
AfterNamespace: 'true'
AfterStruct: 'true'
AfterUnion: 'true'
BeforeCatch: 'true'
BeforeElse: 'true'
IndentBraces: 'false'
}
BreakBeforeBraces: Custom
ColumnLimit: 100
Cpp11BracedListStyle: 'true'
DerivePointerAlignment: 'false'
IndentCaseLabels: 'false'
IndentWidth: 4
PenaltyExcessCharacter: 100000000
PenaltyReturnTypeOnItsOwnLine: 100000000
PointerAlignment: Left
PointerBindsToType: 'true'
SortIncludes: 'false'
SpaceAfterTemplateKeyword: 'false'
SpaceBeforeParens: ControlStatements
TabWidth: 8
UseTab: Never
...

View File

@ -0,0 +1,33 @@
# Apply this style by doing
#
# clang-tidy -fix-errors -config= <source-files>
#
# Running the command without -fix-errors will generate warnings about each
# style violation but won't change them.
Checks: '-*,readability-identifier-naming'
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: CamelCase
- key: readability-identifier-naming.MemberCase
value: CamelCase
- key: readability-identifier-naming.MethodCase
value: CamelCase
- key: readability-identifier-naming.NamespaceCase
value: CamelCase
- key: readability-identifier-naming.ParameterCase
value: lower_case
- key: readability-identifier-naming.PrivateMemberCase
value: CamelCase
- key: readability-identifier-naming.PrivateMemberPrefix
value: '_'
- key: readability-identifier-naming.StaticConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.VariableCase
value: lower_case

View File

@ -0,0 +1,24 @@
name: Build
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgDirectory: ${{ github.workspace}}/vcpkg
runVcpkgInstall: true
- name: CMake generate
run: cmake -B build-release/ -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace}}/vcpkg/scripts/buildsystems/vcpkg.cmake
- name: CMake build
run: cmake --build build-release/ -j

529
Lib/imnodes-master-b2ec254/.gitignore vendored Normal file
View File

@ -0,0 +1,529 @@
### Local build ###
build*/
### ImGui ###
**.ini
# Created by https://www.toptal.com/developers/gitignore/api/sublimetext
# Edit at https://www.toptal.com/developers/gitignore?templates=sublimetext
### SublimeText ###
# Cache files for Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# Workspace files are user-specific
*.sublime-workspace
# Project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using Sublime Text
*.sublime-project
# SFTP configuration file
sftp-config.json
sftp-config-alt*.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
# End of https://www.toptal.com/developers/gitignore/api/sublimetext
# Created by https://www.gitignore.io/api/c,c++
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### C++ ###
# Prerequisites
# Compiled Object files
*.slo
# Precompiled Headers
# Compiled Dynamic libraries
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
# Executables
# End of https://www.gitignore.io/api/c,c++
# Created by https://www.gitignore.io/api/visualstudiocode
### VisualStudioCode ###
.vscode/*
# End of https://www.gitignore.io/api/visualstudiocode
# Created by https://www.gitignore.io/api/premake-gmake
### premake-gmake ###
Makefile
*.make
obj/
# End of https://www.gitignore.io/api/premake-gmake
# Created by https://www.gitignore.io/api/visualstudio
# Edit at https://www.gitignore.io/?templates=visualstudio
### VisualStudio ###
## 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/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# 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/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.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
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# 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/
# End of https://www.gitignore.io/api/visualstudio
# Created by https://www.toptal.com/developers/gitignore/api/vcpkg
# Edit at https://www.toptal.com/developers/gitignore?templates=vcpkg
### vcpkg ###
# Vcpkg
## Manifest Mode
vcpkg_installed/
# End of https://www.toptal.com/developers/gitignore/api/vcpkg
# Created by https://www.toptal.com/developers/gitignore/api/cmake
# Edit at https://www.toptal.com/developers/gitignore?templates=cmake
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
### CMake Patch ###
# External projects
*-prefix/
# End of https://www.toptal.com/developers/gitignore/api/cmake

View File

@ -0,0 +1,3 @@
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git

View File

@ -0,0 +1,107 @@
cmake_minimum_required(VERSION 3.15)
project(imnodes)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# determine whether this is a standalone project or included by other projects
if (NOT DEFINED IMNODES_STANDALONE_PROJECT)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(IMNODES_STANDALONE_PROJECT ON)
else()
set(IMNODES_STANDALONE_PROJECT OFF)
endif ()
endif()
# cmake options
option(IMNODES_EXAMPLES "Build examples" ${IMNODES_STANDALONE_PROJECT})
# allow custom imgui target name since this can vary because imgui doesn't natively include a CMakeLists.txt
if(NOT DEFINED IMNODES_IMGUI_TARGET_NAME)
find_package(imgui CONFIG REQUIRED)
set(IMNODES_IMGUI_TARGET_NAME imgui::imgui)
endif()
if(MSVC)
add_compile_definitions(SDL_MAIN_HANDLED)
add_compile_options(/WX)
# replace existing /W to avoid warning
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
add_compile_options(/W4)
endif()
else()
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()
# Imnodes
add_library(imnodes)
target_sources(imnodes PRIVATE
imnodes.h
imnodes_internal.h
imnodes.cpp)
target_include_directories(imnodes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(imnodes PUBLIC ${IMNODES_IMGUI_TARGET_NAME})
# Example projects
if(IMNODES_EXAMPLES)
find_package(SDL2 CONFIG REQUIRED)
add_executable(colornode
${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/color_node_editor.cpp)
target_link_libraries(colornode imnodes SDL2::SDL2)
if (APPLE)
target_link_libraries(colornode "-framework OpenGL")
elseif(MSVC)
target_link_libraries(colornode "opengl32")
else()
target_link_libraries(colornode X11 Xext GL)
endif()
add_executable(multieditor
${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/multi_editor.cpp)
target_link_libraries(multieditor imnodes SDL2::SDL2)
if (APPLE)
target_link_libraries(multieditor "-framework OpenGL")
elseif(MSVC)
target_link_libraries(multieditor "opengl32")
else()
target_link_libraries(multieditor X11 Xext GL)
endif()
add_executable(saveload
${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/save_load.cpp)
target_link_libraries(saveload imnodes SDL2::SDL2)
if (APPLE)
target_link_libraries(saveload "-framework OpenGL")
elseif(MSVC)
target_link_libraries(saveload "opengl32")
else()
target_link_libraries(saveload X11 Xext GL)
endif()
add_executable(hello
${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example/hello.cpp)
target_link_libraries(hello imnodes SDL2::SDL2)
if (APPLE)
target_link_libraries(hello "-framework OpenGL")
elseif(MSVC)
target_link_libraries(hello "opengl32")
else()
target_link_libraries(hello X11 Xext GL)
endif()
endif()

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Johann Muszynski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,264 @@
<h1 align="center">imnodes</h1>
<p align="center">A small, dependency-free node editor extension for <a href="https://github.com/ocornut/imgui">dear imgui</a>.</p>
<p align="center">
<img src="https://raw.githubusercontent.com/Nelarius/imnodes/master/img/imnodes.gif?token=ADH_jEpqbBrw0nH-BUmOip490dyO2CnRks5cVZllwA%3D%3D">
</p>
[![Build Status](https://github.com/nelarius/imnodes/workflows/Build/badge.svg)](https://github.com/nelarius/imnodes/actions?workflow=Build)
Imnodes aims to provide a simple, immediate-mode interface for creating a node editor within an ImGui window. Imnodes provides simple, customizable building blocks that a user needs to build their node editor.
Features:
* Create nodes, links, and pins in an immediate-mode style. The user controls all the state.
* Nest ImGui widgets inside nodes
* Simple distribution, just copy-paste `imnodes.h`, `imnodes_internal.h`, and `imnodes.cpp` into your project along side ImGui.
## Examples
This repository includes a few example files, under `example/`. They are intended as simple examples giving you an idea of what you can build with imnodes.
If you need to build the examples, you can use the provided CMake script to do so.
```bash
# Initialize the vcpkg submodule
$ git submodule update --init
# Run the generation step and build
$ cmake -B build-release/ -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake
$ cmake --build build-release -- -j
```
Note that this has not been tested on Linux and is likely to fail on the platform.
## A brief tour
Here is a small overview of how the extension is used. For more information on example usage, scroll to the bottom of the README.
Before anything can be done, the library must be initialized. This can be done at the same time as `dear imgui` initialization.
```cpp
ImGui::CreateContext();
ImNodes::CreateContext();
// elsewhere in the code...
ImNodes::DestroyContext();
ImGui::DestroyContext();
```
The node editor is a workspace which contains nodes. The node editor must be instantiated within a window, like any other UI element.
```cpp
ImGui::Begin("node editor");
ImNodes::BeginNodeEditor();
ImNodes::EndNodeEditor();
ImGui::End();
```
Now you should have a workspace with a grid visible in the window. An empty node can now be instantiated:
```cpp
const int hardcoded_node_id = 1;
ImNodes::BeginNodeEditor();
ImNodes::BeginNode(hardcoded_node_id);
ImGui::Dummy(ImVec2(80.0f, 45.0f));
ImNodes::EndNode();
ImNodes::EndNodeEditor();
```
Nodes, like windows in `dear imgui` must be uniquely identified. But we can't use the node titles for identification, because it should be possible to have many nodes of the same name in the workspace. Instead, you just use integers for identification.
Attributes are the UI content of the node. An attribute will have a pin (the little circle) on either side of the node. There are two types of attributes: input, and output attributes. Input attribute pins are on the left side of the node, and output attribute pins are on the right. Like nodes, pins must be uniquely identified.
```cpp
ImNodes::BeginNode(hardcoded_node_id);
const int output_attr_id = 2;
ImNodes::BeginOutputAttribute(output_attr_id);
// in between Begin|EndAttribute calls, you can call ImGui
// UI functions
ImGui::Text("output pin");
ImNodes::EndOutputAttribute();
ImNodes::EndNode();
```
The extension doesn't really care what is in the attribute. It just renders the pin for the attribute, and allows the user to create links between pins.
A title bar can be added to the node using `BeginNodeTitleBar` and `EndNodeTitleBar`. Like attributes, you place your title bar's content between the function calls. Note that these functions have to be called before adding attributes or other `dear imgui` UI elements to the node, since the node's layout is built in order, top-to-bottom.
```cpp
ImNodes::BeginNode(hardcoded_node_id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("output node");
ImNodes::EndNodeTitleBar();
// pins and other node UI content omitted...
ImNodes::EndNode();
```
The user has to render their own links between nodes as well. A link is a curve which connects two attributes. A link is just a pair of attribute ids. And like nodes and attributes, links too have to be identified by unique integer values:
```cpp
std::vector<std::pair<int, int>> links;
// elsewhere in the code...
for (int i = 0; i < links.size(); ++i)
{
const std::pair<int, int> p = links[i];
// in this case, we just use the array index of the link
// as the unique identifier
ImNodes::Link(i, p.first, p.second);
}
```
After `EndNodeEditor` has been called, you can check if a link was created during the frame with the function call `IsLinkCreated`:
```cpp
int start_attr, end_attr;
if (ImNodes::IsLinkCreated(&start_attr, &end_attr))
{
links.push_back(std::make_pair(start_attr, end_attr));
}
```
In addition to checking for new links, you can also check whether UI elements are being hovered over by the mouse cursor:
```cpp
int node_id;
if (ImNodes::IsNodeHovered(&node_id))
{
node_hovered = node_id;
}
```
You can also check to see if any node has been selected. Nodes can be clicked on, or they can be selected by clicking and dragging the box selector over them.
```cpp
// Note that since many nodes can be selected at once, we first need to query the number of
// selected nodes before getting them.
const int num_selected_nodes = ImNodes::NumSelectedNodes();
if (num_selected_nodes > 0)
{
std::vector<int> selected_nodes;
selected_nodes.resize(num_selected_nodes);
ImNodes::GetSelectedNodes(selected_nodes.data());
}
```
See `imnodes.h` for more UI event-related functions.
Like `dear imgui`, the style of the UI can be changed. You can set the color style of individual nodes, pins, and links mid-frame by calling `ImNodes::PushColorStyle` and `ImNodes::PopColorStyle`.
```cpp
// set the titlebar color of an individual node
ImNodes::PushColorStyle(
ImNodesCol_TitleBar, IM_COL32(11, 109, 191, 255));
ImNodes::PushColorStyle(
ImNodesCol_TitleBarSelected, IM_COL32(81, 148, 204, 255));
ImNodes::BeginNode(hardcoded_node_id);
// node internals here...
ImNodes::EndNode();
ImNodes::PopColorStyle();
ImNodes::PopColorStyle();
```
If the style is not being set mid-frame, `ImNodes::GetStyle` can be called instead, and the values can be set into the style array directly.
```cpp
// set the titlebar color for all nodes
ImNodesStyle& style = ImNodes::GetStyle();
style.colors[ImNodesCol_TitleBar] = IM_COL32(232, 27, 86, 255);
style.colors[ImNodesCol_TitleBarSelected] = IM_COL32(241, 108, 146, 255);
```
To handle quicker navigation of large graphs you can use an interactive mini-map overlay. The mini-map can be zoomed and scrolled. Editor nodes will track the panning of the mini-map accordingly.
```cpp
ImGui::Begin("node editor");
ImNodes::BeginNodeEditor();
// add nodes...
// must be called right before EndNodeEditor
ImNodes::MiniMap();
ImNodes::EndNodeEditor();
ImGui::End();
```
The relative sizing and corner location of the mini-map in the editor space can be specified like so:
```cpp
// MiniMap is a square region with a side length that is 20% the largest editor canvas dimension
// See ImNodesMiniMapLocation_ for other corner locations
ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_TopRight);
```
The mini-map also supports limited node hovering customization through a user-defined callback.
```cpp
// User callback
void mini_map_node_hovering_callback(int node_id, void* user_data)
{
ImGui::SetTooltip("This is node %d", node_id);
}
// Later on...
ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_TopRight, mini_map_node_hovering_callback, custom_user_data);
// 'custom_user_data' can be used to supply extra information needed for drawing within the callback
```
## Customizing ImNodes
ImNodes can be customized by providing an `imnodes_config.h` header and specifying defining `IMNODES_USER_CONFIG=imnodes_config.h` when compiling.
It is currently possible to override the type of the minimap hovering callback function. This is useful when generating bindings for another language.
Here's an example imnodes_config.h, which generates a pybind wrapper for the callback.
```cpp
#pragma once
#include <pybind11/functional.h>
namespace pybind11 {
inline bool PyWrapper_Check(PyObject *o) { return true; }
class wrapper : public object {
public:
PYBIND11_OBJECT_DEFAULT(wrapper, object, PyWrapper_Check)
wrapper(void* x) { m_ptr = (PyObject*)x; }
explicit operator bool() const { return m_ptr != nullptr && m_ptr != Py_None; }
};
} //namespace pybind11
namespace py = pybind11;
#define ImNodesMiniMapNodeHoveringCallback py::wrapper
#define ImNodesMiniMapNodeHoveringCallbackUserData py::wrapper
```
## Known issues
* `ImGui::Separator()` spans the current window span. As a result, using a separator inside a node will result in the separator spilling out of the node into the node editor grid.
## Further information
See the `examples/` directory to see library usage in greater detail.
* simple.cpp is a simple hello-world style program which displays two nodes
* save_load.cpp is enables you to add and remove nodes and links, and serializes/deserializes them, so that the program state is retained between restarting the program
* color_node_editor.cpp is a more complete example, which shows how a simple node editor is implemented with a graph.

View File

@ -0,0 +1,715 @@
#include "node_editor.h"
#include "graph.h"
#include <imnodes.h>
#include <imgui.h>
#include <SDL2/SDL_timer.h>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cmath>
#include <vector>
namespace example
{
namespace
{
enum class NodeType
{
add,
multiply,
output,
sine,
time,
value
};
struct Node
{
NodeType type;
float value;
explicit Node(const NodeType t) : type(t), value(0.f) {}
Node(const NodeType t, const float v) : type(t), value(v) {}
};
template<class T>
T clamp(T x, T a, T b)
{
return std::min(b, std::max(x, a));
}
static float current_time_seconds = 0.f;
static bool emulate_three_button_mouse = false;
ImU32 evaluate(const Graph<Node>& graph, const int root_node)
{
std::stack<int> postorder;
dfs_traverse(
graph, root_node, [&postorder](const int node_id) -> void { postorder.push(node_id); });
std::stack<float> value_stack;
while (!postorder.empty())
{
const int id = postorder.top();
postorder.pop();
const Node node = graph.node(id);
switch (node.type)
{
case NodeType::add:
{
const float rhs = value_stack.top();
value_stack.pop();
const float lhs = value_stack.top();
value_stack.pop();
value_stack.push(lhs + rhs);
}
break;
case NodeType::multiply:
{
const float rhs = value_stack.top();
value_stack.pop();
const float lhs = value_stack.top();
value_stack.pop();
value_stack.push(rhs * lhs);
}
break;
case NodeType::sine:
{
const float x = value_stack.top();
value_stack.pop();
const float res = std::abs(std::sin(x));
value_stack.push(res);
}
break;
case NodeType::time:
{
value_stack.push(current_time_seconds);
}
break;
case NodeType::value:
{
// If the edge does not have an edge connecting to another node, then just use the value
// at this node. It means the node's input pin has not been connected to anything and
// the value comes from the node's UI.
if (graph.num_edges_from_node(id) == 0ull)
{
value_stack.push(node.value);
}
}
break;
default:
break;
}
}
// The final output node isn't evaluated in the loop -- instead we just pop
// the three values which should be in the stack.
assert(value_stack.size() == 3ull);
const int b = static_cast<int>(255.f * clamp(value_stack.top(), 0.f, 1.f) + 0.5f);
value_stack.pop();
const int g = static_cast<int>(255.f * clamp(value_stack.top(), 0.f, 1.f) + 0.5f);
value_stack.pop();
const int r = static_cast<int>(255.f * clamp(value_stack.top(), 0.f, 1.f) + 0.5f);
value_stack.pop();
return IM_COL32(r, g, b, 255);
}
class ColorNodeEditor
{
public:
ColorNodeEditor()
: graph_(), nodes_(), root_node_id_(-1),
minimap_location_(ImNodesMiniMapLocation_BottomRight)
{
}
void show()
{
// Update timer context
current_time_seconds = 0.001f * SDL_GetTicks();
auto flags = ImGuiWindowFlags_MenuBar;
// The node editor window
ImGui::Begin("color node editor", NULL, flags);
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Mini-map"))
{
const char* names[] = {
"Top Left",
"Top Right",
"Bottom Left",
"Bottom Right",
};
int locations[] = {
ImNodesMiniMapLocation_TopLeft,
ImNodesMiniMapLocation_TopRight,
ImNodesMiniMapLocation_BottomLeft,
ImNodesMiniMapLocation_BottomRight,
};
for (int i = 0; i < 4; i++)
{
bool selected = minimap_location_ == locations[i];
if (ImGui::MenuItem(names[i], NULL, &selected))
minimap_location_ = locations[i];
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Style"))
{
if (ImGui::MenuItem("Classic"))
{
ImGui::StyleColorsClassic();
ImNodes::StyleColorsClassic();
}
if (ImGui::MenuItem("Dark"))
{
ImGui::StyleColorsDark();
ImNodes::StyleColorsDark();
}
if (ImGui::MenuItem("Light"))
{
ImGui::StyleColorsLight();
ImNodes::StyleColorsLight();
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::TextUnformatted("Edit the color of the output color window using nodes.");
ImGui::Columns(2);
ImGui::TextUnformatted("A -- add node");
ImGui::TextUnformatted("X -- delete selected node or link");
ImGui::NextColumn();
if (ImGui::Checkbox("emulate_three_button_mouse", &emulate_three_button_mouse))
{
ImNodes::GetIO().EmulateThreeButtonMouse.Modifier =
emulate_three_button_mouse ? &ImGui::GetIO().KeyAlt : NULL;
}
ImGui::Columns(1);
ImNodes::BeginNodeEditor();
// Handle new nodes
// These are driven by the user, so we place this code before rendering the nodes
{
const bool open_popup = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
ImNodes::IsEditorHovered() && ImGui::IsKeyReleased(ImGuiKey_A);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
if (!ImGui::IsAnyItemHovered() && open_popup)
{
ImGui::OpenPopup("add node");
}
if (ImGui::BeginPopup("add node"))
{
const ImVec2 click_pos = ImGui::GetMousePosOnOpeningCurrentPopup();
if (ImGui::MenuItem("add"))
{
const Node value(NodeType::value, 0.f);
const Node op(NodeType::add);
UiNode ui_node;
ui_node.type = UiNodeType::add;
ui_node.ui.add.lhs = graph_.insert_node(value);
ui_node.ui.add.rhs = graph_.insert_node(value);
ui_node.id = graph_.insert_node(op);
graph_.insert_edge(ui_node.id, ui_node.ui.add.lhs);
graph_.insert_edge(ui_node.id, ui_node.ui.add.rhs);
nodes_.push_back(ui_node);
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
}
if (ImGui::MenuItem("multiply"))
{
const Node value(NodeType::value, 0.f);
const Node op(NodeType::multiply);
UiNode ui_node;
ui_node.type = UiNodeType::multiply;
ui_node.ui.multiply.lhs = graph_.insert_node(value);
ui_node.ui.multiply.rhs = graph_.insert_node(value);
ui_node.id = graph_.insert_node(op);
graph_.insert_edge(ui_node.id, ui_node.ui.multiply.lhs);
graph_.insert_edge(ui_node.id, ui_node.ui.multiply.rhs);
nodes_.push_back(ui_node);
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
}
if (ImGui::MenuItem("output") && root_node_id_ == -1)
{
const Node value(NodeType::value, 0.f);
const Node out(NodeType::output);
UiNode ui_node;
ui_node.type = UiNodeType::output;
ui_node.ui.output.r = graph_.insert_node(value);
ui_node.ui.output.g = graph_.insert_node(value);
ui_node.ui.output.b = graph_.insert_node(value);
ui_node.id = graph_.insert_node(out);
graph_.insert_edge(ui_node.id, ui_node.ui.output.r);
graph_.insert_edge(ui_node.id, ui_node.ui.output.g);
graph_.insert_edge(ui_node.id, ui_node.ui.output.b);
nodes_.push_back(ui_node);
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
root_node_id_ = ui_node.id;
}
if (ImGui::MenuItem("sine"))
{
const Node value(NodeType::value, 0.f);
const Node op(NodeType::sine);
UiNode ui_node;
ui_node.type = UiNodeType::sine;
ui_node.ui.sine.input = graph_.insert_node(value);
ui_node.id = graph_.insert_node(op);
graph_.insert_edge(ui_node.id, ui_node.ui.sine.input);
nodes_.push_back(ui_node);
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
}
if (ImGui::MenuItem("time"))
{
UiNode ui_node;
ui_node.type = UiNodeType::time;
ui_node.id = graph_.insert_node(Node(NodeType::time));
nodes_.push_back(ui_node);
ImNodes::SetNodeScreenSpacePos(ui_node.id, click_pos);
}
ImGui::EndPopup();
}
ImGui::PopStyleVar();
}
for (const UiNode& node : nodes_)
{
switch (node.type)
{
case UiNodeType::add:
{
const float node_width = 100.f;
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("add");
ImNodes::EndNodeTitleBar();
{
ImNodes::BeginInputAttribute(node.ui.add.lhs);
const float label_width = ImGui::CalcTextSize("left").x;
ImGui::TextUnformatted("left");
if (graph_.num_edges_from_node(node.ui.add.lhs) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat("##hidelabel", &graph_.node(node.ui.add.lhs).value, 0.01f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
{
ImNodes::BeginInputAttribute(node.ui.add.rhs);
const float label_width = ImGui::CalcTextSize("right").x;
ImGui::TextUnformatted("right");
if (graph_.num_edges_from_node(node.ui.add.rhs) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat("##hidelabel", &graph_.node(node.ui.add.rhs).value, 0.01f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
ImGui::Spacing();
{
ImNodes::BeginOutputAttribute(node.id);
const float label_width = ImGui::CalcTextSize("result").x;
ImGui::Indent(node_width - label_width);
ImGui::TextUnformatted("result");
ImNodes::EndOutputAttribute();
}
ImNodes::EndNode();
}
break;
case UiNodeType::multiply:
{
const float node_width = 100.0f;
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("multiply");
ImNodes::EndNodeTitleBar();
{
ImNodes::BeginInputAttribute(node.ui.multiply.lhs);
const float label_width = ImGui::CalcTextSize("left").x;
ImGui::TextUnformatted("left");
if (graph_.num_edges_from_node(node.ui.multiply.lhs) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat(
"##hidelabel", &graph_.node(node.ui.multiply.lhs).value, 0.01f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
{
ImNodes::BeginInputAttribute(node.ui.multiply.rhs);
const float label_width = ImGui::CalcTextSize("right").x;
ImGui::TextUnformatted("right");
if (graph_.num_edges_from_node(node.ui.multiply.rhs) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat(
"##hidelabel", &graph_.node(node.ui.multiply.rhs).value, 0.01f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
ImGui::Spacing();
{
ImNodes::BeginOutputAttribute(node.id);
const float label_width = ImGui::CalcTextSize("result").x;
ImGui::Indent(node_width - label_width);
ImGui::TextUnformatted("result");
ImNodes::EndOutputAttribute();
}
ImNodes::EndNode();
}
break;
case UiNodeType::output:
{
const float node_width = 100.0f;
ImNodes::PushColorStyle(ImNodesCol_TitleBar, IM_COL32(11, 109, 191, 255));
ImNodes::PushColorStyle(ImNodesCol_TitleBarHovered, IM_COL32(45, 126, 194, 255));
ImNodes::PushColorStyle(ImNodesCol_TitleBarSelected, IM_COL32(81, 148, 204, 255));
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("output");
ImNodes::EndNodeTitleBar();
ImGui::Dummy(ImVec2(node_width, 0.f));
{
ImNodes::BeginInputAttribute(node.ui.output.r);
const float label_width = ImGui::CalcTextSize("r").x;
ImGui::TextUnformatted("r");
if (graph_.num_edges_from_node(node.ui.output.r) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat(
"##hidelabel", &graph_.node(node.ui.output.r).value, 0.01f, 0.f, 1.0f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
ImGui::Spacing();
{
ImNodes::BeginInputAttribute(node.ui.output.g);
const float label_width = ImGui::CalcTextSize("g").x;
ImGui::TextUnformatted("g");
if (graph_.num_edges_from_node(node.ui.output.g) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat(
"##hidelabel", &graph_.node(node.ui.output.g).value, 0.01f, 0.f, 1.f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
ImGui::Spacing();
{
ImNodes::BeginInputAttribute(node.ui.output.b);
const float label_width = ImGui::CalcTextSize("b").x;
ImGui::TextUnformatted("b");
if (graph_.num_edges_from_node(node.ui.output.b) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat(
"##hidelabel", &graph_.node(node.ui.output.b).value, 0.01f, 0.f, 1.0f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
ImNodes::EndNode();
ImNodes::PopColorStyle();
ImNodes::PopColorStyle();
ImNodes::PopColorStyle();
}
break;
case UiNodeType::sine:
{
const float node_width = 100.0f;
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("sine");
ImNodes::EndNodeTitleBar();
{
ImNodes::BeginInputAttribute(node.ui.sine.input);
const float label_width = ImGui::CalcTextSize("number").x;
ImGui::TextUnformatted("number");
if (graph_.num_edges_from_node(node.ui.sine.input) == 0ull)
{
ImGui::SameLine();
ImGui::PushItemWidth(node_width - label_width);
ImGui::DragFloat(
"##hidelabel",
&graph_.node(node.ui.sine.input).value,
0.01f,
0.f,
1.0f);
ImGui::PopItemWidth();
}
ImNodes::EndInputAttribute();
}
ImGui::Spacing();
{
ImNodes::BeginOutputAttribute(node.id);
const float label_width = ImGui::CalcTextSize("output").x;
ImGui::Indent(node_width - label_width);
ImGui::TextUnformatted("output");
ImNodes::EndOutputAttribute();
}
ImNodes::EndNode();
}
break;
case UiNodeType::time:
{
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("time");
ImNodes::EndNodeTitleBar();
ImNodes::BeginOutputAttribute(node.id);
ImGui::Text("output");
ImNodes::EndOutputAttribute();
ImNodes::EndNode();
}
break;
}
}
for (const auto& edge : graph_.edges())
{
// If edge doesn't start at value, then it's an internal edge, i.e.
// an edge which links a node's operation to its input. We don't
// want to render node internals with visible links.
if (graph_.node(edge.from).type != NodeType::value)
continue;
ImNodes::Link(edge.id, edge.from, edge.to);
}
ImNodes::MiniMap(0.2f, minimap_location_);
ImNodes::EndNodeEditor();
// Handle new links
// These are driven by Imnodes, so we place the code after EndNodeEditor().
{
int start_attr, end_attr;
if (ImNodes::IsLinkCreated(&start_attr, &end_attr))
{
const NodeType start_type = graph_.node(start_attr).type;
const NodeType end_type = graph_.node(end_attr).type;
const bool valid_link = start_type != end_type;
if (valid_link)
{
// Ensure the edge is always directed from the value to
// whatever produces the value
if (start_type != NodeType::value)
{
std::swap(start_attr, end_attr);
}
graph_.insert_edge(start_attr, end_attr);
}
}
}
// Handle deleted links
{
int link_id;
if (ImNodes::IsLinkDestroyed(&link_id))
{
graph_.erase_edge(link_id);
}
}
{
const int num_selected = ImNodes::NumSelectedLinks();
if (num_selected > 0 && ImGui::IsKeyReleased(ImGuiKey_X))
{
static std::vector<int> selected_links;
selected_links.resize(static_cast<size_t>(num_selected));
ImNodes::GetSelectedLinks(selected_links.data());
for (const int edge_id : selected_links)
{
graph_.erase_edge(edge_id);
}
}
}
{
const int num_selected = ImNodes::NumSelectedNodes();
if (num_selected > 0 && ImGui::IsKeyReleased(ImGuiKey_X))
{
static std::vector<int> selected_nodes;
selected_nodes.resize(static_cast<size_t>(num_selected));
ImNodes::GetSelectedNodes(selected_nodes.data());
for (const int node_id : selected_nodes)
{
graph_.erase_node(node_id);
auto iter = std::find_if(
nodes_.begin(), nodes_.end(), [node_id](const UiNode& node) -> bool {
return node.id == node_id;
});
// Erase any additional internal nodes
switch (iter->type)
{
case UiNodeType::add:
graph_.erase_node(iter->ui.add.lhs);
graph_.erase_node(iter->ui.add.rhs);
break;
case UiNodeType::multiply:
graph_.erase_node(iter->ui.multiply.lhs);
graph_.erase_node(iter->ui.multiply.rhs);
break;
case UiNodeType::output:
graph_.erase_node(iter->ui.output.r);
graph_.erase_node(iter->ui.output.g);
graph_.erase_node(iter->ui.output.b);
root_node_id_ = -1;
break;
case UiNodeType::sine:
graph_.erase_node(iter->ui.sine.input);
break;
default:
break;
}
nodes_.erase(iter);
}
}
}
ImGui::End();
// The color output window
const ImU32 color =
root_node_id_ != -1 ? evaluate(graph_, root_node_id_) : IM_COL32(255, 20, 147, 255);
ImGui::PushStyleColor(ImGuiCol_WindowBg, color);
ImGui::Begin("output color");
ImGui::End();
ImGui::PopStyleColor();
}
private:
enum class UiNodeType
{
add,
multiply,
output,
sine,
time,
};
struct UiNode
{
UiNodeType type;
// The identifying id of the ui node. For add, multiply, sine, and time
// this is the "operation" node id. The additional input nodes are
// stored in the structs.
int id;
union
{
struct
{
int lhs, rhs;
} add;
struct
{
int lhs, rhs;
} multiply;
struct
{
int r, g, b;
} output;
struct
{
int input;
} sine;
} ui;
};
Graph<Node> graph_;
std::vector<UiNode> nodes_;
int root_node_id_;
ImNodesMiniMapLocation minimap_location_;
};
static ColorNodeEditor color_editor;
} // namespace
void NodeEditorInitialize()
{
ImNodesIO& io = ImNodes::GetIO();
io.LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
}
void NodeEditorShow() { color_editor.show(); }
void NodeEditorShutdown() {}
} // namespace example

View File

@ -0,0 +1,357 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <iterator>
#include <stack>
#include <stddef.h>
#include <utility>
#include <vector>
namespace example
{
template<typename ElementType>
struct Span
{
using iterator = ElementType*;
template<typename Container>
Span(Container& c) : begin_(c.data()), end_(begin_ + c.size())
{
}
iterator begin() const { return begin_; }
iterator end() const { return end_; }
private:
iterator begin_;
iterator end_;
};
template<typename ElementType>
class IdMap
{
public:
using iterator = typename std::vector<ElementType>::iterator;
using const_iterator = typename std::vector<ElementType>::const_iterator;
// Iterators
const_iterator begin() const { return elements_.begin(); }
const_iterator end() const { return elements_.end(); }
// Element access
Span<const ElementType> elements() const { return elements_; }
// Capacity
bool empty() const { return sorted_ids_.empty(); }
size_t size() const { return sorted_ids_.size(); }
// Modifiers
std::pair<iterator, bool> insert(int id, const ElementType& element);
std::pair<iterator, bool> insert(int id, ElementType&& element);
size_t erase(int id);
void clear();
// Lookup
iterator find(int id);
const_iterator find(int id) const;
bool contains(int id) const;
private:
std::vector<ElementType> elements_;
std::vector<int> sorted_ids_;
};
template<typename ElementType>
std::pair<typename IdMap<ElementType>::iterator, bool> IdMap<ElementType>::insert(
const int id,
const ElementType& element)
{
auto lower_bound = std::lower_bound(sorted_ids_.begin(), sorted_ids_.end(), id);
if (lower_bound != sorted_ids_.end() && id == *lower_bound)
{
return std::make_pair(
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound)), false);
}
auto insert_element_at =
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound));
sorted_ids_.insert(lower_bound, id);
return std::make_pair(elements_.insert(insert_element_at, element), true);
}
template<typename ElementType>
std::pair<typename IdMap<ElementType>::iterator, bool> IdMap<ElementType>::insert(
const int id,
ElementType&& element)
{
auto lower_bound = std::lower_bound(sorted_ids_.begin(), sorted_ids_.end(), id);
if (lower_bound != sorted_ids_.end() && id == *lower_bound)
{
return std::make_pair(
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound)), false);
}
auto insert_element_at =
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound));
sorted_ids_.insert(lower_bound, id);
return std::make_pair(elements_.insert(insert_element_at, std::move(element)), true);
}
template<typename ElementType>
size_t IdMap<ElementType>::erase(const int id)
{
auto lower_bound = std::lower_bound(sorted_ids_.begin(), sorted_ids_.end(), id);
if (lower_bound == sorted_ids_.end() || id != *lower_bound)
{
return 0ull;
}
auto erase_element_at =
std::next(elements_.begin(), std::distance(sorted_ids_.begin(), lower_bound));
sorted_ids_.erase(lower_bound);
elements_.erase(erase_element_at);
return 1ull;
}
template<typename ElementType>
void IdMap<ElementType>::clear()
{
elements_.clear();
sorted_ids_.clear();
}
template<typename ElementType>
typename IdMap<ElementType>::iterator IdMap<ElementType>::find(const int id)
{
const auto lower_bound = std::lower_bound(sorted_ids_.cbegin(), sorted_ids_.cend(), id);
return (lower_bound == sorted_ids_.cend() || *lower_bound != id)
? elements_.end()
: std::next(elements_.begin(), std::distance(sorted_ids_.cbegin(), lower_bound));
}
template<typename ElementType>
typename IdMap<ElementType>::const_iterator IdMap<ElementType>::find(const int id) const
{
const auto lower_bound = std::lower_bound(sorted_ids_.cbegin(), sorted_ids_.cend(), id);
return (lower_bound == sorted_ids_.cend() || *lower_bound != id)
? elements_.cend()
: std::next(elements_.cbegin(), std::distance(sorted_ids_.cbegin(), lower_bound));
}
template<typename ElementType>
bool IdMap<ElementType>::contains(const int id) const
{
const auto lower_bound = std::lower_bound(sorted_ids_.cbegin(), sorted_ids_.cend(), id);
if (lower_bound == sorted_ids_.cend())
{
return false;
}
return *lower_bound == id;
}
// a very simple directional graph
template<typename NodeType>
class Graph
{
public:
Graph() : current_id_(0), nodes_(), edges_from_node_(), node_neighbors_(), edges_() {}
struct Edge
{
int id;
int from, to;
Edge() = default;
Edge(const int id, const int f, const int t) : id(id), from(f), to(t) {}
inline int opposite(const int n) const { return n == from ? to : from; }
inline bool contains(const int n) const { return n == from || n == to; }
};
// Element access
NodeType& node(int node_id);
const NodeType& node(int node_id) const;
Span<const int> neighbors(int node_id) const;
Span<const Edge> edges() const;
// Capacity
size_t num_edges_from_node(int node_id) const;
// Modifiers
int insert_node(const NodeType& node);
void erase_node(int node_id);
int insert_edge(int from, int to);
void erase_edge(int edge_id);
private:
int current_id_;
// These contains map to the node id
IdMap<NodeType> nodes_;
IdMap<int> edges_from_node_;
IdMap<std::vector<int>> node_neighbors_;
// This container maps to the edge id
IdMap<Edge> edges_;
};
template<typename NodeType>
NodeType& Graph<NodeType>::node(const int id)
{
return const_cast<NodeType&>(static_cast<const Graph*>(this)->node(id));
}
template<typename NodeType>
const NodeType& Graph<NodeType>::node(const int id) const
{
const auto iter = nodes_.find(id);
assert(iter != nodes_.end());
return *iter;
}
template<typename NodeType>
Span<const int> Graph<NodeType>::neighbors(int node_id) const
{
const auto iter = node_neighbors_.find(node_id);
assert(iter != node_neighbors_.end());
return *iter;
}
template<typename NodeType>
Span<const typename Graph<NodeType>::Edge> Graph<NodeType>::edges() const
{
return edges_.elements();
}
template<typename NodeType>
size_t Graph<NodeType>::num_edges_from_node(const int id) const
{
auto iter = edges_from_node_.find(id);
assert(iter != edges_from_node_.end());
return *iter;
}
template<typename NodeType>
int Graph<NodeType>::insert_node(const NodeType& node)
{
const int id = current_id_++;
assert(!nodes_.contains(id));
nodes_.insert(id, node);
edges_from_node_.insert(id, 0);
node_neighbors_.insert(id, std::vector<int>());
return id;
}
template<typename NodeType>
void Graph<NodeType>::erase_node(const int id)
{
// first, remove any potential dangling edges
{
static std::vector<int> edges_to_erase;
for (const Edge& edge : edges_.elements())
{
if (edge.contains(id))
{
edges_to_erase.push_back(edge.id);
}
}
for (const int edge_id : edges_to_erase)
{
erase_edge(edge_id);
}
edges_to_erase.clear();
}
nodes_.erase(id);
edges_from_node_.erase(id);
node_neighbors_.erase(id);
}
template<typename NodeType>
int Graph<NodeType>::insert_edge(const int from, const int to)
{
const int id = current_id_++;
assert(!edges_.contains(id));
assert(nodes_.contains(from));
assert(nodes_.contains(to));
edges_.insert(id, Edge(id, from, to));
// update neighbor count
assert(edges_from_node_.contains(from));
*edges_from_node_.find(from) += 1;
// update neighbor list
assert(node_neighbors_.contains(from));
node_neighbors_.find(from)->push_back(to);
return id;
}
template<typename NodeType>
void Graph<NodeType>::erase_edge(const int edge_id)
{
// This is a bit lazy, we find the pointer here, but we refind it when we erase the edge based
// on id key.
assert(edges_.contains(edge_id));
const Edge& edge = *edges_.find(edge_id);
// update neighbor count
assert(edges_from_node_.contains(edge.from));
int& edge_count = *edges_from_node_.find(edge.from);
assert(edge_count > 0);
edge_count -= 1;
// update neighbor list
{
assert(node_neighbors_.contains(edge.from));
auto neighbors = node_neighbors_.find(edge.from);
auto iter = std::find(neighbors->begin(), neighbors->end(), edge.to);
assert(iter != neighbors->end());
neighbors->erase(iter);
}
edges_.erase(edge_id);
}
template<typename NodeType, typename Visitor>
void dfs_traverse(const Graph<NodeType>& graph, const int start_node, Visitor visitor)
{
std::stack<int> stack;
stack.push(start_node);
while (!stack.empty())
{
const int current_node = stack.top();
stack.pop();
visitor(current_node);
for (const int neighbor : graph.neighbors(current_node))
{
stack.push(neighbor);
}
}
}
} // namespace example

View File

@ -0,0 +1,48 @@
#include "node_editor.h"
#include <imnodes.h>
#include <imgui.h>
namespace example
{
namespace
{
class HelloWorldNodeEditor
{
public:
void show()
{
ImGui::Begin("simple node editor");
ImNodes::BeginNodeEditor();
ImNodes::BeginNode(1);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("simple node :)");
ImNodes::EndNodeTitleBar();
ImNodes::BeginInputAttribute(2);
ImGui::Text("input");
ImNodes::EndInputAttribute();
ImNodes::BeginOutputAttribute(3);
ImGui::Indent(40);
ImGui::Text("output");
ImNodes::EndOutputAttribute();
ImNodes::EndNode();
ImNodes::EndNodeEditor();
ImGui::End();
}
};
static HelloWorldNodeEditor editor;
} // namespace
void NodeEditorInitialize() { ImNodes::SetNodeGridSpacePos(1, ImVec2(200.0f, 200.0f)); }
void NodeEditorShow() { editor.show(); }
void NodeEditorShutdown() {}
} // namespace example

View File

@ -0,0 +1,133 @@
#include "node_editor.h"
#include <imgui.h>
#include <imgui_impl_sdl2.h>
#include <imgui_impl_opengl3.h>
#include <imnodes.h>
#include <SDL2/SDL.h>
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <SDL2/SDL_opengles2.h>
#else
#include <SDL2/SDL_opengl.h>
#endif
#include <stdio.h>
int main(int, char**)
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
{
printf("Error: %s\n", SDL_GetError());
return -1;
}
// Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
const char* glsl_version = "#version 100";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__APPLE__)
// GL 3.2 Core + GLSL 150
const char* glsl_version = "#version 150";
SDL_GL_SetAttribute(
SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
// GL 3.0 + GLSL 130
const char* glsl_version = "#version 130";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif
// Create window with graphics context
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_WindowFlags window_flags =
(SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_Window* window = SDL_CreateWindow(
"Dear ImGui SDL2+OpenGL3 example",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
1280,
720,
window_flags);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable vsync
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
ImNodes::CreateContext();
example::NodeEditorInitialize();
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::StyleColorsClassic();
ImNodes::StyleColorsDark();
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
ImGui_ImplOpenGL3_Init(glsl_version);
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Main loop
bool done = false;
while (!done)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT)
done = true;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE &&
event.window.windowID == SDL_GetWindowID(window))
done = true;
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
example::NodeEditorShow();
// Rendering
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(
clear_color.x * clear_color.w,
clear_color.y * clear_color.w,
clear_color.z * clear_color.w,
clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown();
example::NodeEditorShutdown();
ImNodes::DestroyContext();
ImGui::DestroyContext();
SDL_GL_DeleteContext(gl_context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}

View File

@ -0,0 +1,142 @@
#include "node_editor.h"
#include <imnodes.h>
#include <imgui.h>
#include <SDL_scancode.h>
#include <algorithm>
#include <vector>
namespace example
{
namespace
{
struct Node
{
int id;
float value;
Node(const int i, const float v) : id(i), value(v) {}
};
struct Link
{
int id;
int start_attr, end_attr;
};
struct Editor
{
ImNodesEditorContext* context = nullptr;
std::vector<Node> nodes;
std::vector<Link> links;
int current_id = 0;
};
void show_editor(const char* editor_name, Editor& editor)
{
ImNodes::EditorContextSet(editor.context);
ImGui::Begin(editor_name);
ImGui::TextUnformatted("A -- add node");
ImNodes::BeginNodeEditor();
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
ImNodes::IsEditorHovered() && ImGui::IsKeyReleased(ImGuiKey_A))
{
const int node_id = ++editor.current_id;
ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos());
ImNodes::SnapNodeToGrid(node_id);
editor.nodes.push_back(Node(node_id, 0.f));
}
for (Node& node : editor.nodes)
{
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("node");
ImNodes::EndNodeTitleBar();
ImNodes::BeginInputAttribute(node.id << 8);
ImGui::TextUnformatted("input");
ImNodes::EndInputAttribute();
ImNodes::BeginStaticAttribute(node.id << 16);
ImGui::PushItemWidth(120.0f);
ImGui::DragFloat("value", &node.value, 0.01f);
ImGui::PopItemWidth();
ImNodes::EndStaticAttribute();
ImNodes::BeginOutputAttribute(node.id << 24);
const float text_width = ImGui::CalcTextSize("output").x;
ImGui::Indent(120.f + ImGui::CalcTextSize("value").x - text_width);
ImGui::TextUnformatted("output");
ImNodes::EndOutputAttribute();
ImNodes::EndNode();
}
for (const Link& link : editor.links)
{
ImNodes::Link(link.id, link.start_attr, link.end_attr);
}
ImNodes::EndNodeEditor();
{
Link link;
if (ImNodes::IsLinkCreated(&link.start_attr, &link.end_attr))
{
link.id = ++editor.current_id;
editor.links.push_back(link);
}
}
{
int link_id;
if (ImNodes::IsLinkDestroyed(&link_id))
{
auto iter = std::find_if(
editor.links.begin(), editor.links.end(), [link_id](const Link& link) -> bool {
return link.id == link_id;
});
assert(iter != editor.links.end());
editor.links.erase(iter);
}
}
ImGui::End();
}
Editor editor1;
Editor editor2;
} // namespace
void NodeEditorInitialize()
{
editor1.context = ImNodes::EditorContextCreate();
editor2.context = ImNodes::EditorContextCreate();
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
ImNodesIO& io = ImNodes::GetIO();
io.LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
io.MultipleSelectModifier.Modifier = &ImGui::GetIO().KeyCtrl;
ImNodesStyle& style = ImNodes::GetStyle();
style.Flags |= ImNodesStyleFlags_GridLinesPrimary | ImNodesStyleFlags_GridSnapping;
}
void NodeEditorShow()
{
show_editor("editor1", editor1);
show_editor("editor2", editor2);
}
void NodeEditorShutdown()
{
ImNodes::PopAttributeFlag();
ImNodes::EditorContextFree(editor1.context);
ImNodes::EditorContextFree(editor2.context);
}
} // namespace example

View File

@ -0,0 +1,8 @@
#pragma once
namespace example
{
void NodeEditorInitialize();
void NodeEditorShow();
void NodeEditorShutdown();
} // namespace example

View File

@ -0,0 +1,206 @@
#include "node_editor.h"
#include <imnodes.h>
#include <imgui.h>
#include <SDL_keycode.h>
#include <algorithm>
#include <cassert>
#include <fstream>
#include <ios> // for std::streamsize
#include <stddef.h>
#include <vector>
namespace example
{
namespace
{
struct Node
{
int id;
float value;
Node() = default;
Node(const int i, const float v) : id(i), value(v) {}
};
struct Link
{
int id;
int start_attr, end_attr;
};
class SaveLoadEditor
{
public:
SaveLoadEditor() : nodes_(), links_(), current_id_(0) {}
void show()
{
ImGui::Begin("Save & load example");
ImGui::TextUnformatted("A -- add node");
ImGui::TextUnformatted(
"Close the executable and rerun it -- your nodes should be exactly "
"where you left them!");
ImNodes::BeginNodeEditor();
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
ImNodes::IsEditorHovered() && ImGui::IsKeyReleased(ImGuiKey_A))
{
const int node_id = ++current_id_;
ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos());
nodes_.push_back(Node(node_id, 0.f));
}
for (Node& node : nodes_)
{
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("node");
ImNodes::EndNodeTitleBar();
ImNodes::BeginInputAttribute(node.id << 8);
ImGui::TextUnformatted("input");
ImNodes::EndInputAttribute();
ImNodes::BeginStaticAttribute(node.id << 16);
ImGui::PushItemWidth(120.f);
ImGui::DragFloat("value", &node.value, 0.01f);
ImGui::PopItemWidth();
ImNodes::EndStaticAttribute();
ImNodes::BeginOutputAttribute(node.id << 24);
const float text_width = ImGui::CalcTextSize("output").x;
ImGui::Indent(120.f + ImGui::CalcTextSize("value").x - text_width);
ImGui::TextUnformatted("output");
ImNodes::EndOutputAttribute();
ImNodes::EndNode();
}
for (const Link& link : links_)
{
ImNodes::Link(link.id, link.start_attr, link.end_attr);
}
ImNodes::EndNodeEditor();
{
Link link;
if (ImNodes::IsLinkCreated(&link.start_attr, &link.end_attr))
{
link.id = ++current_id_;
links_.push_back(link);
}
}
{
int link_id;
if (ImNodes::IsLinkDestroyed(&link_id))
{
auto iter =
std::find_if(links_.begin(), links_.end(), [link_id](const Link& link) -> bool {
return link.id == link_id;
});
assert(iter != links_.end());
links_.erase(iter);
}
}
ImGui::End();
}
void save()
{
// Save the internal imnodes state
ImNodes::SaveCurrentEditorStateToIniFile("save_load.ini");
// Dump our editor state as bytes into a file
std::fstream fout(
"save_load.bytes", std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
// copy the node vector to file
const size_t num_nodes = nodes_.size();
fout.write(
reinterpret_cast<const char*>(&num_nodes),
static_cast<std::streamsize>(sizeof(size_t)));
fout.write(
reinterpret_cast<const char*>(nodes_.data()),
static_cast<std::streamsize>(sizeof(Node) * num_nodes));
// copy the link vector to file
const size_t num_links = links_.size();
fout.write(
reinterpret_cast<const char*>(&num_links),
static_cast<std::streamsize>(sizeof(size_t)));
fout.write(
reinterpret_cast<const char*>(links_.data()),
static_cast<std::streamsize>(sizeof(Link) * num_links));
// copy the current_id to file
fout.write(
reinterpret_cast<const char*>(&current_id_), static_cast<std::streamsize>(sizeof(int)));
}
void load()
{
// Load the internal imnodes state
ImNodes::LoadCurrentEditorStateFromIniFile("save_load.ini");
// Load our editor state into memory
std::fstream fin("save_load.bytes", std::ios_base::in | std::ios_base::binary);
if (!fin.is_open())
{
return;
}
// copy nodes into memory
size_t num_nodes;
fin.read(reinterpret_cast<char*>(&num_nodes), static_cast<std::streamsize>(sizeof(size_t)));
nodes_.resize(num_nodes);
fin.read(
reinterpret_cast<char*>(nodes_.data()),
static_cast<std::streamsize>(sizeof(Node) * num_nodes));
// copy links into memory
size_t num_links;
fin.read(reinterpret_cast<char*>(&num_links), static_cast<std::streamsize>(sizeof(size_t)));
links_.resize(num_links);
fin.read(
reinterpret_cast<char*>(links_.data()),
static_cast<std::streamsize>(sizeof(Link) * num_links));
// copy current_id into memory
fin.read(reinterpret_cast<char*>(&current_id_), static_cast<std::streamsize>(sizeof(int)));
}
private:
std::vector<Node> nodes_;
std::vector<Link> links_;
int current_id_;
};
static SaveLoadEditor editor;
} // namespace
void NodeEditorInitialize()
{
ImNodes::GetIO().LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
editor.load();
}
void NodeEditorShow() { editor.show(); }
void NodeEditorShutdown()
{
ImNodes::PopAttributeFlag();
editor.save();
}
} // namespace example

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,438 @@
#pragma once
#include <stddef.h>
#include <imgui.h>
#ifdef IMNODES_USER_CONFIG
#include IMNODES_USER_CONFIG
#endif
#ifndef IMNODES_NAMESPACE
#define IMNODES_NAMESPACE ImNodes
#endif
typedef int ImNodesCol; // -> enum ImNodesCol_
typedef int ImNodesStyleVar; // -> enum ImNodesStyleVar_
typedef int ImNodesStyleFlags; // -> enum ImNodesStyleFlags_
typedef int ImNodesPinShape; // -> enum ImNodesPinShape_
typedef int ImNodesAttributeFlags; // -> enum ImNodesAttributeFlags_
typedef int ImNodesMiniMapLocation; // -> enum ImNodesMiniMapLocation_
enum ImNodesCol_
{
ImNodesCol_NodeBackground = 0,
ImNodesCol_NodeBackgroundHovered,
ImNodesCol_NodeBackgroundSelected,
ImNodesCol_NodeOutline,
ImNodesCol_TitleBar,
ImNodesCol_TitleBarHovered,
ImNodesCol_TitleBarSelected,
ImNodesCol_Link,
ImNodesCol_LinkHovered,
ImNodesCol_LinkSelected,
ImNodesCol_Pin,
ImNodesCol_PinHovered,
ImNodesCol_BoxSelector,
ImNodesCol_BoxSelectorOutline,
ImNodesCol_GridBackground,
ImNodesCol_GridLine,
ImNodesCol_GridLinePrimary,
ImNodesCol_MiniMapBackground,
ImNodesCol_MiniMapBackgroundHovered,
ImNodesCol_MiniMapOutline,
ImNodesCol_MiniMapOutlineHovered,
ImNodesCol_MiniMapNodeBackground,
ImNodesCol_MiniMapNodeBackgroundHovered,
ImNodesCol_MiniMapNodeBackgroundSelected,
ImNodesCol_MiniMapNodeOutline,
ImNodesCol_MiniMapLink,
ImNodesCol_MiniMapLinkSelected,
ImNodesCol_MiniMapCanvas,
ImNodesCol_MiniMapCanvasOutline,
ImNodesCol_COUNT
};
enum ImNodesStyleVar_
{
ImNodesStyleVar_GridSpacing = 0,
ImNodesStyleVar_NodeCornerRounding,
ImNodesStyleVar_NodePadding,
ImNodesStyleVar_NodeBorderThickness,
ImNodesStyleVar_LinkThickness,
ImNodesStyleVar_LinkLineSegmentsPerLength,
ImNodesStyleVar_LinkHoverDistance,
ImNodesStyleVar_PinCircleRadius,
ImNodesStyleVar_PinQuadSideLength,
ImNodesStyleVar_PinTriangleSideLength,
ImNodesStyleVar_PinLineThickness,
ImNodesStyleVar_PinHoverRadius,
ImNodesStyleVar_PinOffset,
ImNodesStyleVar_MiniMapPadding,
ImNodesStyleVar_MiniMapOffset,
ImNodesStyleVar_COUNT
};
enum ImNodesStyleFlags_
{
ImNodesStyleFlags_None = 0,
ImNodesStyleFlags_NodeOutline = 1 << 0,
ImNodesStyleFlags_GridLines = 1 << 2,
ImNodesStyleFlags_GridLinesPrimary = 1 << 3,
ImNodesStyleFlags_GridSnapping = 1 << 4
};
enum ImNodesPinShape_
{
ImNodesPinShape_Circle,
ImNodesPinShape_CircleFilled,
ImNodesPinShape_Triangle,
ImNodesPinShape_TriangleFilled,
ImNodesPinShape_Quad,
ImNodesPinShape_QuadFilled
};
// This enum controls the way the attribute pins behave.
enum ImNodesAttributeFlags_
{
ImNodesAttributeFlags_None = 0,
// Allow detaching a link by left-clicking and dragging the link at a pin it is connected to.
// NOTE: the user has to actually delete the link for this to work. A deleted link can be
// detected by calling IsLinkDestroyed() after EndNodeEditor().
ImNodesAttributeFlags_EnableLinkDetachWithDragClick = 1 << 0,
// Visual snapping of an in progress link will trigger IsLink Created/Destroyed events. Allows
// for previewing the creation of a link while dragging it across attributes. See here for demo:
// https://github.com/Nelarius/imnodes/issues/41#issuecomment-647132113 NOTE: the user has to
// actually delete the link for this to work. A deleted link can be detected by calling
// IsLinkDestroyed() after EndNodeEditor().
ImNodesAttributeFlags_EnableLinkCreationOnSnap = 1 << 1
};
struct ImNodesIO
{
struct EmulateThreeButtonMouse
{
EmulateThreeButtonMouse();
// The keyboard modifier to use in combination with mouse left click to pan the editor view.
// Set to NULL by default. To enable this feature, set the modifier to point to a boolean
// indicating the state of a modifier. For example,
//
// ImNodes::GetIO().EmulateThreeButtonMouse.Modifier = &ImGui::GetIO().KeyAlt;
const bool* Modifier;
} EmulateThreeButtonMouse;
struct LinkDetachWithModifierClick
{
LinkDetachWithModifierClick();
// Pointer to a boolean value indicating when the desired modifier is pressed. Set to NULL
// by default. To enable the feature, set the modifier to point to a boolean indicating the
// state of a modifier. For example,
//
// ImNodes::GetIO().LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;
//
// Left-clicking a link with this modifier pressed will detach that link. NOTE: the user has
// to actually delete the link for this to work. A deleted link can be detected by calling
// IsLinkDestroyed() after EndNodeEditor().
const bool* Modifier;
} LinkDetachWithModifierClick;
struct MultipleSelectModifier
{
MultipleSelectModifier();
// Pointer to a boolean value indicating when the desired modifier is pressed. Set to NULL
// by default. To enable the feature, set the modifier to point to a boolean indicating the
// state of a modifier. For example,
//
// ImNodes::GetIO().MultipleSelectModifier.Modifier = &ImGui::GetIO().KeyCtrl;
//
// Left-clicking a node with this modifier pressed will add the node to the list of
// currently selected nodes. If this value is NULL, the Ctrl key will be used.
const bool* Modifier;
} MultipleSelectModifier;
// Holding alt mouse button pans the node area, by default middle mouse button will be used
// Set based on ImGuiMouseButton values
int AltMouseButton;
// Panning speed when dragging an element and mouse is outside the main editor view.
float AutoPanningSpeed;
ImNodesIO();
};
struct ImNodesStyle
{
float GridSpacing;
float NodeCornerRounding;
ImVec2 NodePadding;
float NodeBorderThickness;
float LinkThickness;
float LinkLineSegmentsPerLength;
float LinkHoverDistance;
// The following variables control the look and behavior of the pins. The default size of each
// pin shape is balanced to occupy approximately the same surface area on the screen.
// The circle radius used when the pin shape is either ImNodesPinShape_Circle or
// ImNodesPinShape_CircleFilled.
float PinCircleRadius;
// The quad side length used when the shape is either ImNodesPinShape_Quad or
// ImNodesPinShape_QuadFilled.
float PinQuadSideLength;
// The equilateral triangle side length used when the pin shape is either
// ImNodesPinShape_Triangle or ImNodesPinShape_TriangleFilled.
float PinTriangleSideLength;
// The thickness of the line used when the pin shape is not filled.
float PinLineThickness;
// The radius from the pin's center position inside of which it is detected as being hovered
// over.
float PinHoverRadius;
// Offsets the pins' positions from the edge of the node to the outside of the node.
float PinOffset;
// Mini-map padding size between mini-map edge and mini-map content.
ImVec2 MiniMapPadding;
// Mini-map offset from the screen side.
ImVec2 MiniMapOffset;
// By default, ImNodesStyleFlags_NodeOutline and ImNodesStyleFlags_Gridlines are enabled.
ImNodesStyleFlags Flags;
// Set these mid-frame using Push/PopColorStyle. You can index this color array with with a
// ImNodesCol value.
unsigned int Colors[ImNodesCol_COUNT];
ImNodesStyle();
};
enum ImNodesMiniMapLocation_
{
ImNodesMiniMapLocation_BottomLeft,
ImNodesMiniMapLocation_BottomRight,
ImNodesMiniMapLocation_TopLeft,
ImNodesMiniMapLocation_TopRight,
};
struct ImGuiContext;
struct ImVec2;
struct ImNodesContext;
// An editor context corresponds to a set of nodes in a single workspace (created with a single
// Begin/EndNodeEditor pair)
//
// By default, the library creates an editor context behind the scenes, so using any of the imnodes
// functions doesn't require you to explicitly create a context.
struct ImNodesEditorContext;
// Callback type used to specify special behavior when hovering a node in the minimap
#ifndef ImNodesMiniMapNodeHoveringCallback
typedef void (*ImNodesMiniMapNodeHoveringCallback)(int, void*);
#endif
#ifndef ImNodesMiniMapNodeHoveringCallbackUserData
typedef void* ImNodesMiniMapNodeHoveringCallbackUserData;
#endif
namespace IMNODES_NAMESPACE
{
// Call this function if you are compiling imnodes in to a dll, separate from ImGui. Calling this
// function sets the GImGui global variable, which is not shared across dll boundaries.
void SetImGuiContext(ImGuiContext* ctx);
ImNodesContext* CreateContext();
void DestroyContext(ImNodesContext* ctx = NULL); // NULL = destroy current context
ImNodesContext* GetCurrentContext();
void SetCurrentContext(ImNodesContext* ctx);
ImNodesEditorContext* EditorContextCreate();
void EditorContextFree(ImNodesEditorContext*);
void EditorContextSet(ImNodesEditorContext*);
ImVec2 EditorContextGetPanning();
void EditorContextResetPanning(const ImVec2& pos);
void EditorContextMoveToNode(const int node_id);
ImNodesIO& GetIO();
// Returns the global style struct. See the struct declaration for default values.
ImNodesStyle& GetStyle();
// Style presets matching the dear imgui styles of the same name. If dest is NULL, the active
// context's ImNodesStyle instance will be used as the destination.
void StyleColorsDark(ImNodesStyle* dest = NULL); // on by default
void StyleColorsClassic(ImNodesStyle* dest = NULL);
void StyleColorsLight(ImNodesStyle* dest = NULL);
// The top-level function call. Call this before calling BeginNode/EndNode. Calling this function
// will result the node editor grid workspace being rendered.
void BeginNodeEditor();
void EndNodeEditor();
// Add a navigable minimap to the editor; call before EndNodeEditor after all
// nodes and links have been specified
void MiniMap(
const float minimap_size_fraction = 0.2f,
const ImNodesMiniMapLocation location = ImNodesMiniMapLocation_TopLeft,
const ImNodesMiniMapNodeHoveringCallback node_hovering_callback = NULL,
const ImNodesMiniMapNodeHoveringCallbackUserData node_hovering_callback_data = NULL);
// Use PushColorStyle and PopColorStyle to modify ImNodesStyle::Colors mid-frame.
void PushColorStyle(ImNodesCol item, unsigned int color);
void PopColorStyle();
void PushStyleVar(ImNodesStyleVar style_item, float value);
void PushStyleVar(ImNodesStyleVar style_item, const ImVec2& value);
void PopStyleVar(int count = 1);
// id can be any positive or negative integer, but INT_MIN is currently reserved for internal use.
void BeginNode(int id);
void EndNode();
ImVec2 GetNodeDimensions(int id);
// Place your node title bar content (such as the node title, using ImGui::Text) between the
// following function calls. These functions have to be called before adding any attributes, or the
// layout of the node will be incorrect.
void BeginNodeTitleBar();
void EndNodeTitleBar();
// Attributes are ImGui UI elements embedded within the node. Attributes can have pin shapes
// rendered next to them. Links are created between pins.
//
// The activity status of an attribute can be checked via the IsAttributeActive() and
// IsAnyAttributeActive() function calls. This is one easy way of checking for any changes made to
// an attribute's drag float UI, for instance.
//
// Each attribute id must be unique.
// Create an input attribute block. The pin is rendered on left side.
void BeginInputAttribute(int id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled);
void EndInputAttribute();
// Create an output attribute block. The pin is rendered on the right side.
void BeginOutputAttribute(int id, ImNodesPinShape shape = ImNodesPinShape_CircleFilled);
void EndOutputAttribute();
// Create a static attribute block. A static attribute has no pin, and therefore can't be linked to
// anything. However, you can still use IsAttributeActive() and IsAnyAttributeActive() to check for
// attribute activity.
void BeginStaticAttribute(int id);
void EndStaticAttribute();
// Push a single AttributeFlags value. By default, only AttributeFlags_None is set.
void PushAttributeFlag(ImNodesAttributeFlags flag);
void PopAttributeFlag();
// Render a link between attributes.
// The attributes ids used here must match the ids used in Begin(Input|Output)Attribute function
// calls. The order of start_attr and end_attr doesn't make a difference for rendering the link.
void Link(int id, int start_attribute_id, int end_attribute_id);
// Enable or disable the ability to click and drag a specific node.
void SetNodeDraggable(int node_id, const bool draggable);
// The node's position can be expressed in three coordinate systems:
// * screen space coordinates, -- the origin is the upper left corner of the window.
// * editor space coordinates -- the origin is the upper left corner of the node editor window
// * grid space coordinates, -- the origin is the upper left corner of the node editor window,
// translated by the current editor panning vector (see EditorContextGetPanning() and
// EditorContextResetPanning())
// Use the following functions to get and set the node's coordinates in these coordinate systems.
void SetNodeScreenSpacePos(int node_id, const ImVec2& screen_space_pos);
void SetNodeEditorSpacePos(int node_id, const ImVec2& editor_space_pos);
void SetNodeGridSpacePos(int node_id, const ImVec2& grid_pos);
ImVec2 GetNodeScreenSpacePos(const int node_id);
ImVec2 GetNodeEditorSpacePos(const int node_id);
ImVec2 GetNodeGridSpacePos(const int node_id);
// If ImNodesStyleFlags_GridSnapping is enabled, snap the specified node's origin to the grid.
void SnapNodeToGrid(int node_id);
// Returns true if the current node editor canvas is being hovered over by the mouse, and is not
// blocked by any other windows.
bool IsEditorHovered();
// The following functions return true if a UI element is being hovered over by the mouse cursor.
// Assigns the id of the UI element being hovered over to the function argument. Use these functions
// after EndNodeEditor() has been called.
bool IsNodeHovered(int* node_id);
bool IsLinkHovered(int* link_id);
bool IsPinHovered(int* attribute_id);
// Use The following two functions to query the number of selected nodes or links in the current
// editor. Use after calling EndNodeEditor().
int NumSelectedNodes();
int NumSelectedLinks();
// Get the selected node/link ids. The pointer argument should point to an integer array with at
// least as many elements as the respective NumSelectedNodes/NumSelectedLinks function call
// returned.
void GetSelectedNodes(int* node_ids);
void GetSelectedLinks(int* link_ids);
// Clears the list of selected nodes/links. Useful if you want to delete a selected node or link.
void ClearNodeSelection();
void ClearLinkSelection();
// Use the following functions to add or remove individual nodes or links from the current editors
// selection. Note that all functions require the id to be an existing valid id for this editor.
// Select-functions has the precondition that the object is currently considered unselected.
// Clear-functions has the precondition that the object is currently considered selected.
// Preconditions listed above can be checked via IsNodeSelected/IsLinkSelected if not already
// known.
void SelectNode(int node_id);
void ClearNodeSelection(int node_id);
bool IsNodeSelected(int node_id);
void SelectLink(int link_id);
void ClearLinkSelection(int link_id);
bool IsLinkSelected(int link_id);
// Was the previous attribute active? This will continuously return true while the left mouse button
// is being pressed over the UI content of the attribute.
bool IsAttributeActive();
// Was any attribute active? If so, sets the active attribute id to the output function argument.
bool IsAnyAttributeActive(int* attribute_id = NULL);
// Use the following functions to query a change of state for an existing link, or new link. Call
// these after EndNodeEditor().
// Did the user start dragging a new link from a pin?
bool IsLinkStarted(int* started_at_attribute_id);
// Did the user drop the dragged link before attaching it to a pin?
// There are two different kinds of situations to consider when handling this event:
// 1) a link which is created at a pin and then dropped
// 2) an existing link which is detached from a pin and then dropped
// Use the including_detached_links flag to control whether this function triggers when the user
// detaches a link and drops it.
bool IsLinkDropped(int* started_at_attribute_id = NULL, bool including_detached_links = true);
// Did the user finish creating a new link?
bool IsLinkCreated(
int* started_at_attribute_id,
int* ended_at_attribute_id,
bool* created_from_snap = NULL);
bool IsLinkCreated(
int* started_at_node_id,
int* started_at_attribute_id,
int* ended_at_node_id,
int* ended_at_attribute_id,
bool* created_from_snap = NULL);
// Was an existing link detached from a pin by the user? The detached link's id is assigned to the
// output argument link_id.
bool IsLinkDestroyed(int* link_id);
// Use the following functions to write the editor context's state to a string, or directly to a
// file. The editor context is serialized in the INI file format.
const char* SaveCurrentEditorStateToIniString(size_t* data_size = NULL);
const char* SaveEditorStateToIniString(
const ImNodesEditorContext* editor,
size_t* data_size = NULL);
void LoadCurrentEditorStateFromIniString(const char* data, size_t data_size);
void LoadEditorStateFromIniString(ImNodesEditorContext* editor, const char* data, size_t data_size);
void SaveCurrentEditorStateToIniFile(const char* file_name);
void SaveEditorStateToIniFile(const ImNodesEditorContext* editor, const char* file_name);
void LoadCurrentEditorStateFromIniFile(const char* file_name);
void LoadEditorStateFromIniFile(ImNodesEditorContext* editor, const char* file_name);
} // namespace IMNODES_NAMESPACE

View File

@ -0,0 +1,500 @@
#pragma once
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include "imnodes.h"
#include <limits.h>
// the structure of this file:
//
// [SECTION] internal enums
// [SECTION] internal data structures
// [SECTION] global and editor context structs
// [SECTION] object pool implementation
struct ImNodesContext;
extern ImNodesContext* GImNodes;
// [SECTION] internal enums
typedef int ImNodesScope;
typedef int ImNodesAttributeType;
typedef int ImNodesUIState;
typedef int ImNodesClickInteractionType;
typedef int ImNodesLinkCreationType;
enum ImNodesScope_
{
ImNodesScope_None = 1,
ImNodesScope_Editor = 1 << 1,
ImNodesScope_Node = 1 << 2,
ImNodesScope_Attribute = 1 << 3
};
enum ImNodesAttributeType_
{
ImNodesAttributeType_None,
ImNodesAttributeType_Input,
ImNodesAttributeType_Output
};
enum ImNodesUIState_
{
ImNodesUIState_None = 0,
ImNodesUIState_LinkStarted = 1 << 0,
ImNodesUIState_LinkDropped = 1 << 1,
ImNodesUIState_LinkCreated = 1 << 2
};
enum ImNodesClickInteractionType_
{
ImNodesClickInteractionType_Node,
ImNodesClickInteractionType_Link,
ImNodesClickInteractionType_LinkCreation,
ImNodesClickInteractionType_Panning,
ImNodesClickInteractionType_BoxSelection,
ImNodesClickInteractionType_ImGuiItem,
ImNodesClickInteractionType_None
};
enum ImNodesLinkCreationType_
{
ImNodesLinkCreationType_Standard,
ImNodesLinkCreationType_FromDetach
};
// [SECTION] internal data structures
// The object T must have the following interface:
//
// struct T
// {
// T();
//
// int id;
// };
template<typename T>
struct ImObjectPool
{
ImVector<T> Pool;
ImVector<bool> InUse;
ImVector<int> FreeList;
ImGuiStorage IdMap;
ImObjectPool() : Pool(), InUse(), FreeList(), IdMap() {}
};
// Emulates std::optional<int> using the sentinel value `INVALID_INDEX`.
struct ImOptionalIndex
{
ImOptionalIndex() : _Index(INVALID_INDEX) {}
ImOptionalIndex(const int value) : _Index(value) {}
// Observers
inline bool HasValue() const { return _Index != INVALID_INDEX; }
inline int Value() const
{
IM_ASSERT(HasValue());
return _Index;
}
// Modifiers
inline ImOptionalIndex& operator=(const int value)
{
_Index = value;
return *this;
}
inline void Reset() { _Index = INVALID_INDEX; }
inline bool operator==(const ImOptionalIndex& rhs) const { return _Index == rhs._Index; }
inline bool operator==(const int rhs) const { return _Index == rhs; }
inline bool operator!=(const ImOptionalIndex& rhs) const { return _Index != rhs._Index; }
inline bool operator!=(const int rhs) const { return _Index != rhs; }
static const int INVALID_INDEX = -1;
private:
int _Index;
};
struct ImNodeData
{
int Id;
ImVec2 Origin; // The node origin is in editor space
ImRect TitleBarContentRect;
ImRect Rect;
struct
{
ImU32 Background, BackgroundHovered, BackgroundSelected, Outline, Titlebar, TitlebarHovered,
TitlebarSelected;
} ColorStyle;
struct
{
float CornerRounding;
ImVec2 Padding;
float BorderThickness;
} LayoutStyle;
ImVector<int> PinIndices;
bool Draggable;
ImNodeData(const int node_id)
: Id(node_id), Origin(0.0f, 0.0f), TitleBarContentRect(),
Rect(ImVec2(0.0f, 0.0f), ImVec2(0.0f, 0.0f)), ColorStyle(), LayoutStyle(), PinIndices(),
Draggable(true)
{
}
~ImNodeData() { Id = INT_MIN; }
};
struct ImPinData
{
int Id;
int ParentNodeIdx;
ImRect AttributeRect;
ImNodesAttributeType Type;
ImNodesPinShape Shape;
ImVec2 Pos; // screen-space coordinates
int Flags;
struct
{
ImU32 Background, Hovered;
} ColorStyle;
ImPinData(const int pin_id)
: Id(pin_id), ParentNodeIdx(), AttributeRect(), Type(ImNodesAttributeType_None),
Shape(ImNodesPinShape_CircleFilled), Pos(), Flags(ImNodesAttributeFlags_None),
ColorStyle()
{
}
};
struct ImLinkData
{
int Id;
int StartPinIdx, EndPinIdx;
struct
{
ImU32 Base, Hovered, Selected;
} ColorStyle;
ImLinkData(const int link_id) : Id(link_id), StartPinIdx(), EndPinIdx(), ColorStyle() {}
};
struct ImClickInteractionState
{
ImNodesClickInteractionType Type;
struct
{
int StartPinIdx;
ImOptionalIndex EndPinIdx;
ImNodesLinkCreationType Type;
} LinkCreation;
struct
{
ImRect Rect; // Coordinates in grid space
} BoxSelector;
ImClickInteractionState() : Type(ImNodesClickInteractionType_None) {}
};
struct ImNodesColElement
{
ImU32 Color;
ImNodesCol Item;
ImNodesColElement(const ImU32 c, const ImNodesCol s) : Color(c), Item(s) {}
};
struct ImNodesStyleVarElement
{
ImNodesStyleVar Item;
float FloatValue[2];
ImNodesStyleVarElement(const ImNodesStyleVar variable, const float value) : Item(variable)
{
FloatValue[0] = value;
}
ImNodesStyleVarElement(const ImNodesStyleVar variable, const ImVec2 value) : Item(variable)
{
FloatValue[0] = value.x;
FloatValue[1] = value.y;
}
};
// [SECTION] global and editor context structs
struct ImNodesEditorContext
{
ImObjectPool<ImNodeData> Nodes;
ImObjectPool<ImPinData> Pins;
ImObjectPool<ImLinkData> Links;
ImVector<int> NodeDepthOrder;
// ui related fields
ImVec2 Panning;
ImVec2 AutoPanningDelta;
// Minimum and maximum extents of all content in grid space. Valid after final
// ImNodes::EndNode() call.
ImRect GridContentBounds;
ImVector<int> SelectedNodeIndices;
ImVector<int> SelectedLinkIndices;
// Relative origins of selected nodes for snapping of dragged nodes
ImVector<ImVec2> SelectedNodeOffsets;
// Offset of the primary node origin relative to the mouse cursor.
ImVec2 PrimaryNodeOffset;
ImClickInteractionState ClickInteraction;
// Mini-map state set by MiniMap()
bool MiniMapEnabled;
ImNodesMiniMapLocation MiniMapLocation;
float MiniMapSizeFraction;
ImNodesMiniMapNodeHoveringCallback MiniMapNodeHoveringCallback;
ImNodesMiniMapNodeHoveringCallbackUserData MiniMapNodeHoveringCallbackUserData;
// Mini-map state set during EndNodeEditor() call
ImRect MiniMapRectScreenSpace;
ImRect MiniMapContentScreenSpace;
float MiniMapScaling;
ImNodesEditorContext()
: Nodes(), Pins(), Links(), Panning(0.f, 0.f), SelectedNodeIndices(), SelectedLinkIndices(),
SelectedNodeOffsets(), PrimaryNodeOffset(0.f, 0.f), ClickInteraction(),
MiniMapEnabled(false), MiniMapSizeFraction(0.0f), MiniMapNodeHoveringCallback(NULL),
MiniMapNodeHoveringCallbackUserData(NULL), MiniMapScaling(0.0f)
{
}
};
struct ImNodesContext
{
ImNodesEditorContext* DefaultEditorCtx;
ImNodesEditorContext* EditorCtx;
// Canvas draw list and helper state
ImDrawList* CanvasDrawList;
ImGuiStorage NodeIdxToSubmissionIdx;
ImVector<int> NodeIdxSubmissionOrder;
ImVector<int> NodeIndicesOverlappingWithMouse;
ImVector<int> OccludedPinIndices;
// Canvas extents
ImVec2 CanvasOriginScreenSpace;
ImRect CanvasRectScreenSpace;
// Debug helpers
ImNodesScope CurrentScope;
// Configuration state
ImNodesIO Io;
ImNodesStyle Style;
ImVector<ImNodesColElement> ColorModifierStack;
ImVector<ImNodesStyleVarElement> StyleModifierStack;
ImGuiTextBuffer TextBuffer;
int CurrentAttributeFlags;
ImVector<int> AttributeFlagStack;
// UI element state
int CurrentNodeIdx;
int CurrentPinIdx;
int CurrentAttributeId;
ImOptionalIndex HoveredNodeIdx;
ImOptionalIndex HoveredLinkIdx;
ImOptionalIndex HoveredPinIdx;
ImOptionalIndex DeletedLinkIdx;
ImOptionalIndex SnapLinkIdx;
// Event helper state
// TODO: this should be a part of a state machine, and not a member of the global struct.
// Unclear what parts of the code this relates to.
int ImNodesUIState;
int ActiveAttributeId;
bool ActiveAttribute;
// ImGui::IO cache
ImVec2 MousePos;
bool LeftMouseClicked;
bool LeftMouseReleased;
bool AltMouseClicked;
bool LeftMouseDragging;
bool AltMouseDragging;
float AltMouseScrollDelta;
bool MultipleSelectModifier;
};
namespace IMNODES_NAMESPACE
{
static inline ImNodesEditorContext& EditorContextGet()
{
// No editor context was set! Did you forget to call ImNodes::CreateContext()?
IM_ASSERT(GImNodes->EditorCtx != NULL);
return *GImNodes->EditorCtx;
}
// [SECTION] ObjectPool implementation
template<typename T>
static inline int ObjectPoolFind(const ImObjectPool<T>& objects, const int id)
{
const int index = objects.IdMap.GetInt(static_cast<ImGuiID>(id), -1);
return index;
}
template<typename T>
static inline void ObjectPoolUpdate(ImObjectPool<T>& objects)
{
for (int i = 0; i < objects.InUse.size(); ++i)
{
const int id = objects.Pool[i].Id;
if (!objects.InUse[i] && objects.IdMap.GetInt(id, -1) == i)
{
objects.IdMap.SetInt(id, -1);
objects.FreeList.push_back(i);
(objects.Pool.Data + i)->~T();
}
}
}
template<>
inline void ObjectPoolUpdate(ImObjectPool<ImNodeData>& nodes)
{
for (int i = 0; i < nodes.InUse.size(); ++i)
{
if (nodes.InUse[i])
{
nodes.Pool[i].PinIndices.clear();
}
else
{
const int id = nodes.Pool[i].Id;
if (nodes.IdMap.GetInt(id, -1) == i)
{
// Remove node idx form depth stack the first time we detect that this idx slot is
// unused
ImVector<int>& depth_stack = EditorContextGet().NodeDepthOrder;
const int* const elem = depth_stack.find(i);
IM_ASSERT(elem != depth_stack.end());
depth_stack.erase(elem);
nodes.IdMap.SetInt(id, -1);
nodes.FreeList.push_back(i);
(nodes.Pool.Data + i)->~ImNodeData();
}
}
}
}
template<typename T>
static inline void ObjectPoolReset(ImObjectPool<T>& objects)
{
if (!objects.InUse.empty())
{
memset(objects.InUse.Data, 0, objects.InUse.size_in_bytes());
}
}
template<typename T>
static inline int ObjectPoolFindOrCreateIndex(ImObjectPool<T>& objects, const int id)
{
int index = objects.IdMap.GetInt(static_cast<ImGuiID>(id), -1);
// Construct new object
if (index == -1)
{
if (objects.FreeList.empty())
{
index = objects.Pool.size();
IM_ASSERT(objects.Pool.size() == objects.InUse.size());
const int new_size = objects.Pool.size() + 1;
objects.Pool.resize(new_size);
objects.InUse.resize(new_size);
}
else
{
index = objects.FreeList.back();
objects.FreeList.pop_back();
}
IM_PLACEMENT_NEW(objects.Pool.Data + index) T(id);
objects.IdMap.SetInt(static_cast<ImGuiID>(id), index);
}
// Flag it as used
objects.InUse[index] = true;
return index;
}
template<>
inline int ObjectPoolFindOrCreateIndex(ImObjectPool<ImNodeData>& nodes, const int node_id)
{
int node_idx = nodes.IdMap.GetInt(static_cast<ImGuiID>(node_id), -1);
// Construct new node
if (node_idx == -1)
{
if (nodes.FreeList.empty())
{
node_idx = nodes.Pool.size();
IM_ASSERT(nodes.Pool.size() == nodes.InUse.size());
const int new_size = nodes.Pool.size() + 1;
nodes.Pool.resize(new_size);
nodes.InUse.resize(new_size);
}
else
{
node_idx = nodes.FreeList.back();
nodes.FreeList.pop_back();
}
IM_PLACEMENT_NEW(nodes.Pool.Data + node_idx) ImNodeData(node_id);
nodes.IdMap.SetInt(static_cast<ImGuiID>(node_id), node_idx);
ImNodesEditorContext& editor = EditorContextGet();
editor.NodeDepthOrder.push_back(node_idx);
}
// Flag node as used
nodes.InUse[node_idx] = true;
return node_idx;
}
template<typename T>
static inline T& ObjectPoolFindOrCreateObject(ImObjectPool<T>& objects, const int id)
{
const int index = ObjectPoolFindOrCreateIndex(objects, id);
return objects.Pool[index];
}
} // namespace IMNODES_NAMESPACE

View File

@ -0,0 +1,11 @@
{
"name": "imnodes",
"version-string": "0.1.0-dev",
"dependencies": [
"sdl2",
{
"name": "imgui",
"features": [ "sdl2-binding", "opengl3-binding" ]
}
]
}

View File

@ -79,6 +79,7 @@ CXX_INCLUDES_PATHS = \
Inc \
Lib/imgui-1.92.4-docking \
Lib/imgui-1.92.4-docking/backends \
Lib/imnodes-master-b2ec254 \
# C++源文件目录
CXX_SOURCES_PATHS = \
@ -93,7 +94,8 @@ Lib/imgui-1.92.4-docking/imgui_tables.cpp \
Lib/imgui-1.92.4-docking/imgui_widgets.cpp \
Lib/imgui-1.92.4-docking/backends/imgui_impl_dx11.cpp \
Lib/imgui-1.92.4-docking/backends/imgui_impl_win32.cpp \
# Lib/imgui-1.92.4-docking/examples/example_win32_directx11/main.cpp \
\
Lib/imnodes-master-b2ec254/imnodes.cpp \
# Windows 资源文件脚本头文件路径
WIN_RESOURCE_INCLUDES_PATHS = \

View File

@ -3,6 +3,9 @@
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
// ImNodes相关
#include "imnodes.h"
extern "C"
{
// Mingw 相关库
@ -87,6 +90,9 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
// 建立 Dear ImGui 上下文
ImGui::CreateContext();
// 建立ImNodes 上下文
ImNodes::CreateContext();
// 获取ImGui IO 设备配置结构体
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
@ -220,6 +226,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
ImGui_ImplDX11_Shutdown(); // 关闭 ImGui DX11 组件
ImGui_ImplWin32_Shutdown(); // 关闭 ImGui Win32 组件
ImGui::DestroyContext(); // 销毁 ImGui 上下文
ImNodes::DestroyContext(); // 销毁 ImNodes 上下文
CleanupDeviceD3D(); // 清理 Direct 3D 设备
DestroyWindow(hwnd); // 销毁窗体

View File

@ -24,10 +24,30 @@ void UI_Layout()
// 设置窗体相关属性,不允许停靠
window_flags |= ImGuiWindowFlags_NoDocking;
ImGui::Begin("Main Panel", NULL, window_flags);
ImGui::Begin("Main Panel", NULL, window_flags); // 创建Label
ImGui::PopStyleVar(2); // 退出绘制风格栈中的设置项
{
// 退出绘制风格栈中的设置项
ImGui::PopStyleVar(2);
ImNodes::BeginNodeEditor();
{
ImNodes::BeginNode(1);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted("output node");
ImNodes::EndNodeTitleBar();
ImGui::Text("Test Format %%d :%d", 123);
ImGui::Button("Click");
ImGui::SameLine();
static bool check = false;
ImGui::Checkbox("", &check);
ImNodes::EndNode();
}
ImNodes::EndNodeEditor();
}
ImGui::End();