├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── core-nodes ├── CoreDiagram.cpp ├── CoreDiagram.hpp ├── CoreLibrary.cpp ├── CoreLibrary.hpp ├── CoreNode.cpp ├── CoreNode.hpp ├── CoreNodePort.cpp ├── CoreNodePort.hpp ├── FileDialog.cpp ├── FileDialog.hpp ├── GainNode.cpp ├── GainNode.hpp ├── MyApp.cpp ├── MyApp.hpp ├── Notifier.cpp ├── Notifier.hpp ├── TestNode.cpp ├── TestNode.hpp └── main.cpp ├── images └── core-nodes.gif └── untitled.dxdt /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/* 35 | libs/* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Minimum CMake version 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | # Name of the project 5 | project(core-nodes) 6 | 7 | # C++ standard 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_CXX_STANDARD_REQUIRED True) 10 | 11 | include(FetchContent) 12 | # gui-app-template library 13 | FetchContent_Populate( 14 | gui-app-template 15 | GIT_REPOSITORY https://github.com/onurae/gui-app-template.git 16 | GIT_TAG f4f2bdf5fba02811b1a0affa601067cb9cfefd55 # release-0.5.1 17 | SOURCE_DIR ${PROJECT_SOURCE_DIR}/libs/gui-app-template 18 | ) 19 | 20 | # pugixml library 21 | FetchContent_Populate( 22 | pugixml 23 | GIT_REPOSITORY https://github.com/zeux/pugixml.git 24 | GIT_TAG a0e064336317c9347a91224112af9933598714e9 # release-1.13.0 25 | SOURCE_DIR ${PROJECT_SOURCE_DIR}/libs/pugixml-1.13.0 26 | ) 27 | 28 | # Executable 29 | add_subdirectory(libs/gui-app-template) # Add gui-app-template 30 | file(GLOB SOURCES ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}/*.?pp) 31 | file(GLOB SOURCES_XML ${PROJECT_SOURCE_DIR}/libs/pugixml-1.13.0/src/*.?pp) 32 | add_executable(CoreNodes ${SOURCES} ${SOURCES_XML}) 33 | target_link_libraries(CoreNodes 34 | PRIVATE GuiAppTemplate 35 | ) 36 | target_include_directories(CoreNodes 37 | PRIVATE libs/gui-app-template 38 | PRIVATE libs/pugixml-1.13.0/src 39 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Onur AKIN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # core-nodes 2 | 3 | Graphical user interface project for simulating dynamical systems. 4 | 5 | ![](https://github.com/onurae/core-nodes/blob/main/images/core-nodes.gif) 6 | -------------------------------------------------------------------------------- /core-nodes/CoreDiagram.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Diagram * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "CoreDiagram.hpp" 11 | 12 | CoreDiagram::~CoreDiagram() 13 | { 14 | for (const auto& node : coreNodeVec) 15 | { 16 | delete node; 17 | } 18 | } 19 | 20 | void CoreDiagram::Update() 21 | { 22 | UpdateCanvasRect(); 23 | UpdateCanvasScrollZoom(); 24 | UpdateCanvasGrid(ImGui::GetWindowDrawList()); 25 | UpdateNodeFlags(); // Sets hovNode. 26 | Actions(); 27 | DrawCanvasElements(); 28 | PopupMenu(); 29 | } 30 | 31 | void CoreDiagram::Save(pugi::xml_node& xmlNode) const 32 | { 33 | auto node = xmlNode.append_child("diagram"); 34 | SaveFloat(node, "scale", scale); 35 | SaveImVec2(node, "scroll", scroll); 36 | highlightedNode != nullptr ? SaveString(node, "hNode", highlightedNode->GetName()) : SaveString(node, "hNode", ""); 37 | auto exeList = node.append_child("exeList"); 38 | for (const auto& element : exeOrder) 39 | { 40 | exeList.append_child("node").append_attribute("name") = element->GetName().c_str(); 41 | } 42 | auto nodeList = node.append_child("nodeList"); 43 | for (const auto& element : coreNodeVec) 44 | { 45 | element->Save(nodeList); 46 | } 47 | auto linkList = node.append_child("linkList"); 48 | for (const auto& element : linkVec) 49 | { 50 | element.Save(linkList); 51 | } 52 | } 53 | 54 | void CoreDiagram::Load(const pugi::xml_node& xmlNode) 55 | { 56 | auto node = xmlNode.child("diagram"); 57 | state = State::Default; 58 | scale = LoadFloat(node, "scale"); 59 | scroll = LoadImVec2(node, "scroll"); 60 | highlightedNode = nullptr; 61 | hovNode = nullptr; 62 | iNode = nullptr; 63 | iNodeInput = nullptr; 64 | iNodeOutput = nullptr; 65 | rectCanvas = ImRect(); 66 | rectSelecting = ImRect(); 67 | inputFreeLink = ImVec2(); 68 | outputFreeLink = ImVec2(); 69 | for (const auto& element : node.child("nodeList").children("node")) 70 | { 71 | // Add node without building. 72 | coreNodeVec.push_back(coreLib.GetNode(LoadString(element, "libName"), LoadString(element, "name"))); 73 | coreNodeVec.back()->Load(element); 74 | } 75 | auto hName = LoadString(node, "hNode"); 76 | for (const auto element : coreNodeVec) 77 | { 78 | if (element->GetName() == hName) 79 | { 80 | highlightedNode = element; 81 | } 82 | } 83 | for (const auto& element : node.child("linkList").children("link")) 84 | { 85 | linkVec.emplace_back(); 86 | linkVec.back().Load(element); 87 | 88 | // Match pointers 89 | auto inputNodeName = LoadString(element, "inputNode"); 90 | auto outputNodeName = LoadString(element, "outputNode"); 91 | auto inputPortOrder = LoadInt(element, "inputPort"); 92 | auto outputPortOrder = LoadInt(element, "outputPort"); 93 | for (auto coreNode : coreNodeVec) 94 | { 95 | auto coreName = coreNode->GetName(); 96 | if (coreName == inputNodeName) 97 | { 98 | linkVec.back().inputNode = coreNode; 99 | linkVec.back().inputPort = &(coreNode->GetInputVec().at(inputPortOrder)); 100 | } 101 | if (coreName == outputNodeName) 102 | { 103 | linkVec.back().outputNode = coreNode; 104 | linkVec.back().outputPort = &(coreNode->GetOutputVec().at(outputPortOrder)); 105 | } 106 | } 107 | for (auto& link : linkVec) 108 | { 109 | link.inputPort->SetTargetNode(link.outputNode); 110 | link.inputPort->SetTargetNodeOutput(link.outputPort); 111 | } 112 | } 113 | for (const auto& element : node.child("exeList").children("node")) 114 | { 115 | for (const auto nodeElement : coreNodeVec) 116 | { 117 | if (nodeElement->GetName() == element.attribute("name").as_string()) 118 | { 119 | exeOrder.push_back(nodeElement); 120 | } 121 | } 122 | } 123 | } 124 | 125 | void CoreDiagram::DrawExplorer() 126 | { 127 | ImGui::Text("Node Execution Order"); 128 | ImGui::Separator(); 129 | for (int i = 0; i < exeOrder.size(); i++) 130 | { 131 | auto item = exeOrder.at(i); 132 | ImGui::Selectable(item->GetName().c_str()); 133 | if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) 134 | { 135 | int iNext = i + (ImGui::GetMouseDragDelta(0).y < 0.0f ? -1 : 1); 136 | if (iNext >= 0 && iNext < exeOrder.size()) 137 | { 138 | exeOrder.at(i) = exeOrder.at(iNext); 139 | exeOrder.at(iNext) = item; 140 | ImGui::ResetMouseDragDelta(); 141 | modifFlag = true; 142 | } 143 | } 144 | } 145 | } 146 | 147 | void CoreDiagram::DrawProperties() 148 | { 149 | if (highlightedNode != nullptr) 150 | { 151 | highlightedNode->DrawProperties(coreNodeVec); 152 | } 153 | for (const auto& element : coreNodeVec) 154 | { 155 | if (element->GetModifFlag() == true) 156 | { 157 | modifFlag = true; 158 | element->ResetModifFlag(); 159 | } 160 | } 161 | } 162 | 163 | void CoreDiagram::Actions() 164 | { 165 | MouseMove(); 166 | if (ImGui::IsMouseDoubleClicked(0)) 167 | { 168 | MouseLeftButtonDoubleClick(); 169 | return; 170 | } 171 | if (ImGui::IsMouseDoubleClicked(1)) 172 | { 173 | MouseRightButtonDoubleClick(); 174 | return; 175 | } 176 | if (ImGui::IsMouseClicked(0)) 177 | { 178 | MouseLeftButtonSingleClick(); // Set dragging states. 179 | return; 180 | } 181 | if (ImGui::IsMouseDragging(0)) 182 | { 183 | MouseLeftButtonDrag(); // Set selecting state. 184 | return; 185 | } 186 | if (ImGui::IsMouseReleased(0)) 187 | { 188 | MouseLeftButtonRelease(); 189 | return; 190 | } 191 | if (ImGui::IsMouseReleased(1)) 192 | { 193 | MouseRightButtonRelease(); 194 | return; 195 | } 196 | if (ImGui::IsKeyPressed(ImGuiKey_Delete)) 197 | { 198 | KeyboardPressDelete(); 199 | return; 200 | } 201 | if (ImGui::IsKeyPressed(ImGuiKey_Space)) 202 | { 203 | FitToWindow(); 204 | } 205 | } 206 | 207 | void CoreDiagram::MouseMove() 208 | { 209 | bool condition = (state == State::None) || (state == State::Default) || (state == State::HoveringNode) || 210 | (state == State::HoveringInput) || (state == State::HoveringOutput); 211 | if (condition == true) 212 | { 213 | if (ImGui::IsWindowHovered() == false) 214 | { 215 | state = State::None; 216 | iNode = nullptr; 217 | iNodeInput = nullptr; 218 | iNodeOutput = nullptr; 219 | } 220 | else if (hovNode == nullptr) 221 | { 222 | state = State::Default; 223 | iNode = nullptr; 224 | iNodeInput = nullptr; 225 | iNodeOutput = nullptr; 226 | } 227 | else 228 | { 229 | iNode = hovNode; 230 | iNodeInput = nullptr; 231 | iNodeOutput = nullptr; 232 | 233 | for (auto& input : hovNode->GetInputVec()) 234 | { 235 | if (input.GetFlagSet().HasFlag(PortFlag::Hovered)) 236 | { 237 | iNodeInput = &input; 238 | state = State::HoveringInput; // SetState:HoveringInput 239 | break; 240 | } 241 | } 242 | 243 | for (auto& output : hovNode->GetOutputVec()) 244 | { 245 | if (output.GetFlagSet().HasFlag(PortFlag::Hovered)) 246 | { 247 | iNodeOutput = &output; 248 | state = State::HoveringOutput; // SetState:HoveringOutput 249 | break; 250 | } 251 | } 252 | 253 | if (iNodeInput == nullptr && iNodeOutput == nullptr) 254 | { 255 | state = State::HoveringNode; // SetState:HoveringNode 256 | } 257 | } 258 | } 259 | } 260 | 261 | void CoreDiagram::MouseLeftButtonDoubleClick() 262 | { 263 | if (state == State::HoveringNode) 264 | { 265 | ImVec2 offset = position + scroll; 266 | ImRect titleArea = hovNode->GetRectNodeTitle(); 267 | titleArea.Min *= scale; 268 | titleArea.Max *= scale; 269 | titleArea.Translate(offset); 270 | if (titleArea.Contains(mousePos)) 271 | { 272 | ImRect rNode = hovNode->GetRectNode(); 273 | if (hovNode->GetFlagSet().HasFlag(NodeFlag::Collapsed)) 274 | { 275 | hovNode->GetFlagSet().UnsetFlag(NodeFlag::Collapsed); 276 | rNode.Max.y += hovNode->GetBodyHeight(); 277 | hovNode->SetRectNode(rNode); 278 | //iNode->Translate(ImVec2(0.0f, iNode->GetBodyHeight() * -0.5f)); 279 | } 280 | else 281 | { 282 | hovNode->GetFlagSet().SetFlag(NodeFlag::Collapsed); 283 | rNode.Max.y -= hovNode->GetBodyHeight(); 284 | hovNode->SetRectNode(rNode); 285 | //iNode->Translate(ImVec2(0.0f, iNode->GetBodyHeight() * 0.5f)); 286 | } 287 | modifFlag = true; 288 | } 289 | } 290 | else if (state == State::HoveringInput) // Break connection 291 | { 292 | if (iNodeInput->GetTargetNode()) 293 | { 294 | iNodeInput->BreakLink(); 295 | EraseLink(iNodeInput); 296 | modifFlag = true; 297 | state = State::DragingInput; // To be able to drag input after breaking. 298 | } 299 | } 300 | else if (state == State::HoveringOutput) // Break all connections 301 | { 302 | if (iNodeOutput->GetLinkNum() != 0) 303 | { 304 | for (const auto& node : coreNodeVec) 305 | { 306 | for (auto& input : node->GetInputVec()) 307 | { 308 | if (input.GetTargetNodeOutput() == iNodeOutput) 309 | { 310 | input.BreakLink(); 311 | EraseLink(&input); 312 | modifFlag = true; 313 | } 314 | } 315 | } 316 | } 317 | } 318 | } 319 | 320 | void CoreDiagram::MouseRightButtonDoubleClick() 321 | { 322 | if (state == State::HoveringNode) 323 | { 324 | ImVec2 offset = position + scroll; 325 | ImRect titleArea = hovNode->GetRectNodeTitle(); 326 | titleArea.Min *= scale; 327 | titleArea.Max *= scale; 328 | titleArea.Translate(offset); 329 | if (titleArea.Contains(mousePos) && hovNode->GetFlagSet().HasFlag(NodeFlag::Collapsed) == false) 330 | { 331 | hovNode->InvertPort(); 332 | modifFlag = true; 333 | } 334 | } 335 | else if (state == State::HoveringNode) 336 | { 337 | //IM_ASSERT(hovered_node); 338 | // 339 | //if (hovered_node->GetFlagSet().HasFlag(NodeFlag::Disabled)) 340 | // hovered_node->GetFlagSet().UnsetFlag(NodeFlag::Disabled); 341 | //else 342 | // hovered_node->GetFlagSet().SetFlag(NodeFlag::Disabled); 343 | } 344 | else if (state == State::HoveringInput) 345 | { 346 | // show connected port? one click is ok TODO. 347 | } 348 | } 349 | 350 | void CoreDiagram::MouseLeftButtonSingleClick() 351 | { 352 | if (state == State::Default) 353 | { 354 | bool selected = false; 355 | for (const auto& node : coreNodeVec) 356 | { 357 | if (node->GetFlagSet().HasFlag(NodeFlag::Selected)) 358 | { 359 | selected = true; 360 | } 361 | node->GetFlagSet().UnsetFlag(NodeFlag::Selected | NodeFlag::Hovered); 362 | } 363 | if (highlightedNode && selected == false) 364 | { 365 | highlightedNode->GetFlagSet().UnsetFlag(NodeFlag::Highlighted); 366 | highlightedNode = nullptr; 367 | modifFlag = true; 368 | } 369 | } 370 | else if (state == State::HoveringNode) 371 | { 372 | const ImGuiIO& io = ImGui::GetIO(); 373 | if (io.KeyCtrl) 374 | { 375 | iNode->GetFlagSet().FlipFlag(NodeFlag::Selected); 376 | } 377 | if (io.KeyShift) 378 | { 379 | iNode->GetFlagSet().SetFlag(NodeFlag::Selected); 380 | } 381 | 382 | if (iNode->GetFlagSet().HasFlag(NodeFlag::Selected) == false) 383 | { 384 | HighlightNode(); 385 | } 386 | else 387 | { 388 | SortNodeOrder(); 389 | } 390 | state = State::Draging; // SetState:Draging 391 | distFromClickToCenter = (mousePos - rectCanvas.GetTL() - scroll) / scale - iNode->GetRectNode().GetCenter(); 392 | } 393 | else if (state == State::HoveringInput) 394 | { 395 | if (iNodeInput->GetTargetNode() == nullptr) 396 | { 397 | state = State::DragingInput; // SetState:DragingInput 398 | } 399 | else 400 | { 401 | state = State::Draging; 402 | 403 | const ImGuiIO& io = ImGui::GetIO(); 404 | if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) 405 | { 406 | mNodeDrag = true; 407 | } 408 | } 409 | } 410 | else if (state == State::HoveringOutput) 411 | { 412 | state = State::DragingOutput; // SetState:DragingOutput 413 | } 414 | } 415 | 416 | void CoreDiagram::MouseRightButtonSingleClick() 417 | { 418 | } 419 | 420 | void CoreDiagram::MouseLeftButtonDrag() 421 | { 422 | const ImGuiIO& io = ImGui::GetIO(); 423 | if (state == State::Default) 424 | { 425 | if (io.KeyShift == false) 426 | { 427 | for (const auto& node : coreNodeVec) 428 | { 429 | node->GetFlagSet().UnsetFlag(NodeFlag::Selected); 430 | } 431 | } 432 | state = State::Selecting; // SetState:Selecting 433 | } 434 | else if (state == State::Selecting) 435 | { 436 | const ImVec2 pos = mousePos - ImGui::GetMouseDragDelta(0); 437 | rectSelecting.Min = ImMin(pos, mousePos); 438 | rectSelecting.Max = ImMax(pos, mousePos); 439 | } 440 | else if (state == State::Draging) 441 | { 442 | if (iNode->GetFlagSet().HasFlag(NodeFlag::Selected) == false) // If draging node is an unselected node, drag only that node. 443 | { 444 | DragNodeSingle(); 445 | } 446 | else // If draging node is selected, drag all selected nodes. 447 | { 448 | DragNodeMulti(); 449 | } 450 | if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) 451 | { 452 | mNodeDrag = true; 453 | } 454 | } 455 | else if (state == State::DragingInput) 456 | { 457 | iNodeOutput = nullptr; 458 | if (hovNode) 459 | { 460 | for (auto& output : hovNode->GetOutputVec()) 461 | { 462 | const FlagSet& fSet = output.GetFlagSet(); 463 | if (fSet.HasFlag(PortFlag::Hovered | PortFlag::Connectible)) 464 | { 465 | iNodeOutput = &output; 466 | } 467 | } 468 | } 469 | ImVec2 offset = position + scroll; 470 | ImVec2 p1 = offset + (iNodeInput->GetPosition() * scale); 471 | ImVec2 p4 = iNodeOutput ? (offset + (iNodeOutput->GetPosition() * scale)) : mousePos; 472 | inputFreeLink = ImVec2(p1.x, p1.y); 473 | outputFreeLink = ImVec2(p4.x, p4.y); 474 | } 475 | else if (state == State::DragingOutput) 476 | { 477 | iNodeInput = nullptr; 478 | if (hovNode) 479 | { 480 | for (auto& input : hovNode->GetInputVec()) 481 | { 482 | const FlagSet& fSet = input.GetFlagSet(); 483 | if (fSet.HasFlag(PortFlag::Hovered | PortFlag::Connectible)) 484 | { 485 | iNodeInput = &input; 486 | } 487 | } 488 | } 489 | ImVec2 offset = position + scroll; 490 | ImVec2 p1 = offset + (iNodeOutput->GetPosition() * scale); 491 | ImVec2 p4 = iNodeInput ? (offset + (iNodeInput->GetPosition() * scale)) : mousePos; 492 | inputFreeLink = ImVec2(p4.x, p4.y); 493 | outputFreeLink = ImVec2(p1.x, p1.y); 494 | } 495 | } 496 | 497 | void CoreDiagram::MouseLeftButtonRelease() 498 | { 499 | if (state == State::None) 500 | { 501 | if (coreLib.IsLeafClicked() == true) // If a leaf clicked then released on the outside of canvas. 502 | { 503 | coreLib.SetLeafClickedFalse(); 504 | } 505 | } 506 | else if (state == State::Default) 507 | { 508 | if (coreLib.IsLeafClicked() == true) // If a leaf clicked then released on the canvas, create the leaf. 509 | { 510 | auto leafName = coreLib.GetSelectedLeaf(); 511 | auto newNode = coreLib.GetNode(leafName, CreateUniqueName(leafName)); 512 | if (newNode != nullptr) 513 | { 514 | newNode->Build(); 515 | ImVec2 pos = (mousePos - scroll - position) / scale; 516 | newNode->Translate(pos - newNode->GetRectNode().GetCenter()); 517 | newNode->GetFlagSet().SetFlag(NodeFlag::Visible | NodeFlag::Hovered | NodeFlag::Highlighted); 518 | coreNodeVec.push_back(newNode); 519 | exeOrder.push_back(newNode); 520 | if (highlightedNode != nullptr) 521 | { 522 | highlightedNode->GetFlagSet().UnsetFlag(NodeFlag::Highlighted); 523 | } 524 | highlightedNode = newNode; 525 | modifFlag = true; 526 | coreLib.SetLeafClickedFalse(); 527 | } 528 | else 529 | { 530 | Notifier::Add(Notif(Notif::Type::ERROR, "[" + leafName + "]" + " not found in the library!")); 531 | coreLib.SetLeafClickedFalse(); 532 | } 533 | } 534 | } 535 | else if (state == State::Selecting) 536 | { 537 | rectSelecting = ImRect(); 538 | SortNodeOrder(); 539 | state = State::Default; 540 | } 541 | else if (state == State::Draging) 542 | { 543 | state = State::HoveringNode; //SetState:HoveringNode 544 | if (mNodeDrag == true) 545 | { 546 | modifFlag = true; 547 | mNodeDrag = false; 548 | } 549 | } 550 | else if (state == State::DragingInput || state == State::DragingOutput) 551 | { 552 | if (iNodeInput && iNodeOutput) // About to connect. 553 | { 554 | if (iNodeInput->GetTargetNodeOutput()) // If input is already connected to another output. 555 | { 556 | iNodeInput->BreakLink(); 557 | EraseLink(iNodeInput); 558 | } 559 | 560 | Link link; 561 | if (state == State::DragingInput) 562 | { 563 | link.inputNode = iNode; 564 | link.outputNode = hovNode; 565 | iNodeInput->SetTargetNode(hovNode); 566 | } 567 | if (state == State::DragingOutput) 568 | { 569 | link.inputNode = hovNode; 570 | link.outputNode = iNode; 571 | iNodeInput->SetTargetNode(iNode); 572 | } 573 | iNodeInput->SetTargetNodeOutput(iNodeOutput); 574 | iNodeOutput->IncreaseLinkNum(); 575 | 576 | link.inputPort = iNodeInput; 577 | link.outputPort = iNodeOutput; 578 | linkVec.push_back(link); 579 | modifFlag = true; 580 | } 581 | 582 | inputFreeLink = ImVec2(); 583 | outputFreeLink = ImVec2(); 584 | state = State::Default; 585 | } 586 | } 587 | 588 | void CoreDiagram::MouseRightButtonRelease() 589 | { 590 | // Open diagram popup menu. 591 | const ImGuiIO& io = ImGui::GetIO(); 592 | if (state != State::None && rectCanvas.Contains(mousePos) && ImGui::IsMouseDown(0) == false && ImGui::IsMouseReleased(1) && iNode == nullptr) 593 | { 594 | if (io.MouseDragMaxDistanceSqr[1] < (io.MouseDragThreshold * io.MouseDragThreshold)) 595 | { 596 | bool selected = false; 597 | for (int node_idx = 0; node_idx < coreNodeVec.size(); ++node_idx) 598 | { 599 | if (coreNodeVec[node_idx]->GetFlagSet().HasFlag(NodeFlag::Selected)) 600 | { 601 | selected = true; 602 | break; 603 | } 604 | } 605 | if (false == selected) 606 | { 607 | ImGui::OpenPopup("DiagramPopupMenu"); 608 | } 609 | } 610 | } 611 | } 612 | 613 | void CoreDiagram::KeyboardPressDelete() 614 | { 615 | std::vector unselectedNodes; 616 | std::vector selectedNodes; 617 | unselectedNodes.reserve(coreNodeVec.size()); 618 | selectedNodes.reserve(coreNodeVec.size()); 619 | for (const auto& node : coreNodeVec) 620 | { 621 | if (node->GetFlagSet().HasFlag(NodeFlag::Selected)) 622 | { 623 | // Delete all input connections. 624 | for (auto& input : node->GetInputVec()) 625 | { 626 | if (input.GetTargetNode() != nullptr) 627 | { 628 | input.BreakLink(); 629 | EraseLink(&input); 630 | } 631 | } 632 | // Delete all connections that are connected with outputs. 633 | for (const auto& otherNode : coreNodeVec) 634 | { 635 | for (auto& input : otherNode->GetInputVec()) 636 | { 637 | if (input.GetTargetNode() != nullptr && input.GetTargetNode() == node) 638 | { 639 | input.BreakLink(); 640 | EraseLink(&input); 641 | } 642 | } 643 | } 644 | // Check all output connections. 645 | for (const auto& output : node->GetOutputVec()) 646 | { 647 | IM_ASSERT(output.GetLinkNum() == 0); 648 | } 649 | 650 | if (node == highlightedNode) 651 | { 652 | highlightedNode = nullptr; 653 | } 654 | if (node == hovNode) 655 | { 656 | hovNode = nullptr; 657 | } 658 | if (node == iNode) 659 | { 660 | iNode = nullptr; 661 | } 662 | selectedNodes.push_back(node); 663 | modifFlag = true; 664 | } 665 | else 666 | { 667 | unselectedNodes.push_back(node); 668 | } 669 | } 670 | 671 | // Delete selected nodes 672 | for (const auto& node : selectedNodes) 673 | { 674 | auto it = std::find(exeOrder.begin(), exeOrder.end(), node); 675 | exeOrder.erase(it); 676 | delete node; 677 | } 678 | coreNodeVec = unselectedNodes; 679 | } 680 | 681 | void CoreDiagram::DrawCanvasElements() 682 | { 683 | SetLinkProperties(); 684 | DrawLinks(); 685 | DrawNodes(); 686 | DrawFreeLink(); 687 | DrawSelecting(); 688 | PrintInfo(); 689 | } 690 | 691 | void CoreDiagram::DrawNodes() const 692 | { 693 | ImDrawList* drawList = ImGui::GetWindowDrawList(); 694 | ImGui::SetWindowFontScale(scale); 695 | const ImVec2 offset = position + scroll; 696 | for (const auto& node : coreNodeVec) 697 | { 698 | node->Draw(drawList, offset, scale); 699 | } 700 | ImGui::SetWindowFontScale(1.0f); 701 | } 702 | 703 | void CoreDiagram::DrawSelecting() const 704 | { 705 | ImDrawList* drawList = ImGui::GetWindowDrawList(); 706 | if (state == State::Selecting) 707 | { 708 | drawList->AddRectFilled(rectSelecting.Min, rectSelecting.Max, ImColor(1.0f, 1.0f, 0.0f, 0.05f)); 709 | drawList->AddRect(rectSelecting.Min, rectSelecting.Max, ImColor(1.0f, 1.0f, 1.0f, 0.5f)); 710 | } 711 | } 712 | 713 | void CoreDiagram::PrintInfo() 714 | { 715 | ImGui::SetCursorPos(ImVec2(0.0f, 0.0f)); 716 | ImGui::NewLine(); 717 | 718 | ImGui::NewLine(); 719 | ImGui::Text("Position: %.2f, %.2f", position.x, position.y); 720 | ImGui::Text("Size: %.2f, %.2f", size.x, size.y); 721 | (state != State::None) ? ImGui::Text("Mouse: %.2f, %.2f", mousePos.x, mousePos.y) : ImGui::Text("Mouse: Outside of Canvas"); 722 | ImGui::Text("CanvasTL: %.2f, %.2f", rectCanvas.GetTL().x, rectCanvas.GetTL().y); 723 | ImGui::Text("CanvasBR: %.2f, %.2f", rectCanvas.GetBR().x, rectCanvas.GetBR().y); 724 | ImGui::Text("Scroll: %.2f, %.2f", scroll.x, scroll.y); 725 | ImGui::Text("Scale: %.2f", scale); 726 | 727 | ImGui::NewLine(); 728 | int numberOfVisibleNodes = 0; 729 | for (const auto& node : coreNodeVec) 730 | { 731 | if (node->GetFlagSet().HasFlag(NodeFlag::Visible)) 732 | { 733 | numberOfVisibleNodes += 1; 734 | } 735 | } 736 | ImGui::Text("Visible Nodes #: %d", numberOfVisibleNodes); 737 | 738 | // Only 8 bits. 739 | ImGui::NewLine(); 740 | ImGui::Text("FlagSet Node: %s", iNode ? iNode->GetFlagSet().Get().substr(8 * sizeof(unsigned int) - 8, 8).c_str() : nullptr); 741 | ImGui::Text("FlagSet Input: %s", iNodeInput ? iNodeInput->GetFlagSet().Get().substr(8 * sizeof(unsigned int) - 8, 8).c_str() : nullptr); 742 | ImGui::Text("FlagSet Output: %s", iNodeOutput ? iNodeOutput->GetFlagSet().Get().substr(8 * sizeof(unsigned int) - 8, 8).c_str() : nullptr); 743 | 744 | ImGui::NewLine(); 745 | switch (state) 746 | { 747 | case State::None: ImGui::Text("None"); break; 748 | case State::Default: ImGui::Text("Default"); break; 749 | case State::HoveringNode: ImGui::Text("HoveringNode"); break; 750 | case State::HoveringInput: ImGui::Text("HoveringInput"); break; 751 | case State::HoveringOutput: ImGui::Text("HoveringOutput"); break; 752 | case State::Draging: ImGui::Text("Draging"); break; 753 | case State::DragingInput: ImGui::Text("DragingInput"); break; 754 | case State::DragingOutput: ImGui::Text("DragingOutput"); break; 755 | case State::Selecting: ImGui::Text("Selecting"); break; 756 | default: ImGui::Text("UNKNOWN"); break; 757 | } 758 | ImGui::Text("iNode: %s", iNode ? iNode->GetName().c_str() : nullptr); 759 | ImGui::Text("iNodeInput: %s", iNodeInput ? iNodeInput->GetName().c_str() : nullptr); 760 | ImGui::Text("iNodeOutput: %s", iNodeOutput ? iNodeOutput->GetName().c_str() : nullptr); 761 | 762 | ImGui::NewLine(); 763 | ImGui::Text("Hovered: %s", hovNode ? hovNode->GetName().c_str() : nullptr); 764 | ImGui::Text("Highlighted: %s", highlightedNode ? highlightedNode->GetName().c_str() : nullptr); 765 | 766 | ImGui::NewLine(); 767 | ImGui::Text("iNodeOutput_Link#: %d", iNodeOutput ? iNodeOutput->GetLinkNum() : 0); 768 | 769 | ImGui::NewLine(); 770 | int linkNum = 0; 771 | for (const auto& node : coreNodeVec) 772 | { 773 | for (const auto& input : node->GetInputVec()) 774 | { 775 | if (input.GetTargetNode() != nullptr) 776 | { 777 | linkNum += 1; 778 | } 779 | } 780 | } 781 | if (linkVec.size() != linkNum) 782 | { 783 | ImGui::Text("Error LinkNum-Links"); 784 | } 785 | 786 | ImGui::Text("Total Links #: %d", linkVec.size()); 787 | int numberOfVisibleLinks = 0; 788 | const ImVec2 offset = position + scroll; 789 | for (const auto& link : linkVec) 790 | { 791 | ImVec2 pInput = link.inputPort->GetPosition() * scale + offset; 792 | ImVec2 pOutput = link.outputPort->GetPosition() * scale + offset; 793 | if (IsLinkVisible(pInput, pOutput) == true) 794 | { 795 | numberOfVisibleLinks += 1; 796 | } 797 | } 798 | ImGui::Text("Visible Links #: %d", numberOfVisibleLinks); 799 | 800 | if (iNode != nullptr) 801 | { 802 | ImGui::NewLine(); 803 | ImGui::Text("[wrtOrigin]"); 804 | auto hTL = ImVec2(iNode->GetRectNode().GetTL() + scroll); 805 | auto hBR = ImVec2(iNode->GetRectNode().GetBR() + scroll); 806 | ImGui::Text("iNodeTL: %.2f, %.2f", hTL.x, hTL.y); 807 | ImGui::Text("iNodeBR: %.2f, %.2f", hBR.x, hBR.y); 808 | } 809 | } 810 | 811 | void CoreDiagram::UpdateCanvasRect() 812 | { 813 | mousePos = ImGui::GetMousePos(); 814 | position = ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin(); 815 | size = ImGui::GetWindowContentRegionMax() - ImGui::GetWindowContentRegionMin(); 816 | rectCanvas = ImRect(position, position + size); 817 | } 818 | 819 | void CoreDiagram::UpdateCanvasScrollZoom() 820 | { 821 | const ImGuiIO& io = ImGui::GetIO(); 822 | bool dragCond = state == State::DragingInput || state == State::DragingOutput; 823 | if (state != State::None && (ImGui::IsMouseDown(0) == false || dragCond) && rectCanvas.Contains(mousePos)) 824 | { 825 | if (ImGui::IsMouseDragging(1)) 826 | { 827 | scroll += io.MouseDelta; 828 | } 829 | ImVec2 focus = (mousePos - scroll - position) / scale; 830 | auto zoom = static_cast(io.MouseWheel); 831 | if (zoom < 0) 832 | { 833 | while (zoom < 0) 834 | { 835 | scale = ImMax(scaleMin, scale - deltaScale); 836 | zoom += 1; 837 | } 838 | } 839 | if (zoom > 0) 840 | { 841 | while (zoom > 0) 842 | { 843 | scale = ImMin(scaleMax, scale + deltaScale); 844 | zoom -= 1; 845 | } 846 | } 847 | if (ImGui::IsMouseClicked(2)) 848 | { 849 | scale = 1.0f; 850 | } 851 | ImVec2 shift = scroll + (focus * scale); 852 | scroll += mousePos - shift - position; 853 | } 854 | } 855 | 856 | void CoreDiagram::UpdateCanvasGrid(ImDrawList* drawList) const 857 | { 858 | const float grid = 32.0f * scale; 859 | float x = std::fmodf(scroll.x, grid); 860 | float y = std::fmodf(scroll.y, grid); 861 | auto markX = static_cast(scroll.x / grid); 862 | auto markY = static_cast(scroll.y / grid); 863 | while (x < size.x) 864 | { 865 | ImColor color = markX % 5 ? ImColor(0.5f, 0.5f, 0.5f, 0.1f) : ImColor(1.0f, 1.0f, 1.0f, 0.1f); 866 | drawList->AddLine(ImVec2(x, 0.0f) + position, ImVec2(x, size.y) + position, color, 0.1f); 867 | x += grid; 868 | markX -= 1; 869 | } 870 | while (y < size.y) 871 | { 872 | ImColor color = markY % 5 ? ImColor(0.5f, 0.5f, 0.5f, 0.1f) : ImColor(1.0f, 1.0f, 1.0f, 0.1f); 873 | drawList->AddLine(ImVec2(0.0f, y) + position, ImVec2(size.x, y) + position, color, 0.1f); 874 | y += grid; 875 | markY -= 1; 876 | } 877 | } 878 | 879 | void CoreDiagram::FitToWindow() 880 | { 881 | if (coreNodeVec.empty() == true || rectCanvas.Contains(mousePos) == false) 882 | { 883 | return; 884 | } 885 | 886 | // Rectangle of all nodes. 887 | ImVec2 min = coreNodeVec.at(0)->GetRectNode().GetTL(); 888 | ImVec2 max = coreNodeVec.at(0)->GetRectNode().GetBR(); 889 | for (const auto& element : coreNodeVec) 890 | { 891 | if (auto xTL = element->GetRectNode().GetTL().x; min.x > xTL) { min.x = xTL; } 892 | if (auto yTL = element->GetRectNode().GetTL().y; min.y > yTL) { min.y = yTL; } 893 | if (auto xBR = element->GetRectNode().GetBR().x; max.x < xBR) { max.x = xBR; } 894 | if (auto yBR = element->GetRectNode().GetBR().y; max.y < yBR) { max.y = yBR; } 895 | } 896 | auto rectNodes = ImRect(min, max); 897 | 898 | // Scaling to fit. 899 | auto rX = rectCanvas.GetWidth() / rectNodes.GetWidth(); 900 | auto rY = rectCanvas.GetHeight() / rectNodes.GetHeight(); 901 | auto r = rY < rX ? rY : rX; 902 | scale = r * 0.80f; // Canvas is 1.25x of the rectNodes. 903 | scale = std::floor(scale / deltaScale) * deltaScale; 904 | if (scale >= scaleMax) 905 | { 906 | scale = scaleMax; 907 | } 908 | if (scale <= scaleMin) 909 | { 910 | scale = scaleMin; 911 | Notifier::Add(Notif(Notif::Type::WARNING, "Zoom out limit!", 912 | "There might be nodes outside of the visible canvas, check visible node number.", 5)); 913 | } 914 | 915 | scroll -= rectNodes.GetCenter() * scale + scroll; 916 | scroll = scroll + ImVec2(rectCanvas.GetWidth(), rectCanvas.GetHeight()) * 0.5f; 917 | } 918 | 919 | void CoreDiagram::DragNodeSingle() 920 | { 921 | // Only allow dragging node inside of the canvas. 922 | const ImGuiIO& io = ImGui::GetIO(); 923 | if (rectCanvas.Contains(mousePos) && draggingOutOfCanvas == false) 924 | { 925 | iNode->Translate(io.MouseDelta / scale, false); 926 | } 927 | else if (rectCanvas.Contains(mousePos) && draggingOutOfCanvas == true) 928 | { 929 | iNode->Translate((mousePos - rectCanvas.GetTL() - scroll) / scale - clickPosAtTheEdge, false); 930 | draggingOutOfCanvas = false; 931 | } 932 | else 933 | { 934 | draggingOutOfCanvas = true; 935 | clickPosAtTheEdge = iNode->GetRectNode().GetCenter() + distFromClickToCenter; 936 | } 937 | } 938 | 939 | void CoreDiagram::DragNodeMulti() 940 | { 941 | // Only allow dragging node inside of the canvas. 942 | const ImGuiIO& io = ImGui::GetIO(); 943 | if (rectCanvas.Contains(mousePos) && draggingOutOfCanvas == false) 944 | { 945 | for (const auto& node : coreNodeVec) 946 | { 947 | node->Translate(io.MouseDelta / scale, true); 948 | } 949 | } 950 | else if (rectCanvas.Contains(mousePos) && draggingOutOfCanvas == true) 951 | { 952 | for (const auto& node : coreNodeVec) 953 | { 954 | node->Translate((mousePos - rectCanvas.GetTL() - scroll) / scale - clickPosAtTheEdge, true); 955 | } 956 | draggingOutOfCanvas = false; 957 | } 958 | else 959 | { 960 | draggingOutOfCanvas = true; 961 | clickPosAtTheEdge = iNode->GetRectNode().GetCenter() + distFromClickToCenter; 962 | } 963 | } 964 | 965 | void CoreDiagram::UpdateNodeFlags() 966 | { 967 | hovNode = nullptr; // Important. 968 | if (coreNodeVec.empty()) 969 | { 970 | return; 971 | } 972 | 973 | ImVec2 offset = position + scroll; 974 | ImRect canvas(position, position + size); 975 | for (auto it = coreNodeVec.rbegin(); it != coreNodeVec.rend(); ++it) 976 | { 977 | CoreNode* node = *it; 978 | 979 | // Unset hovered flag 980 | node->GetFlagSet().UnsetFlag(NodeFlag::Hovered); 981 | 982 | // Node and io areas 983 | ImRect nodeArea = node->GetRectNode(); 984 | nodeArea.Min *= scale; 985 | nodeArea.Max *= scale; 986 | nodeArea.Translate(offset); 987 | nodeArea.ClipWith(canvas); 988 | auto inputArea = ImRect(node->GetInputVec().front().GetRectPin().Min, node->GetInputVec().back().GetRectPin().Max); 989 | inputArea.Min *= scale; 990 | inputArea.Max *= scale; 991 | inputArea.Translate(offset); 992 | inputArea.ClipWith(canvas); 993 | auto outputArea = ImRect(node->GetOutputVec().front().GetRectPin().Min, node->GetOutputVec().back().GetRectPin().Max); 994 | outputArea.Min *= scale; 995 | outputArea.Max *= scale; 996 | outputArea.Translate(offset); 997 | outputArea.ClipWith(canvas); 998 | 999 | // Visibility 1000 | if (canvas.Overlaps(nodeArea) || canvas.Overlaps(inputArea) || canvas.Overlaps(outputArea)) 1001 | { 1002 | node->GetFlagSet().SetFlag(NodeFlag::Visible); 1003 | } 1004 | else 1005 | { 1006 | node->GetFlagSet().UnsetFlag(NodeFlag::Visible); 1007 | continue; 1008 | } 1009 | 1010 | // Hovered Node Condition 1011 | if (state != State::None && hovNode == nullptr && 1012 | (nodeArea.Contains(mousePos) || inputArea.Contains(mousePos) || outputArea.Contains(mousePos))) 1013 | { 1014 | hovNode = node; 1015 | hovNode->GetFlagSet().SetFlag(NodeFlag::Hovered); 1016 | } 1017 | 1018 | // Selecting 1019 | if (state == State::Selecting) 1020 | { 1021 | if (rectSelecting.Overlaps(nodeArea)) 1022 | { 1023 | node->GetFlagSet().SetFlag(NodeFlag::Selected); 1024 | continue; 1025 | } 1026 | else if (ImGui::GetIO().KeyShift == false) 1027 | { 1028 | node->GetFlagSet().UnsetFlag(NodeFlag::Selected); 1029 | } 1030 | } 1031 | UpdateInputFlags(node); 1032 | UpdateOutputFlags(node); 1033 | } 1034 | } 1035 | 1036 | void CoreDiagram::UpdateInputFlags(CoreNode* node) 1037 | { 1038 | for (auto& input : node->GetInputVec()) 1039 | { 1040 | if (input.GetType() == PortType::None) 1041 | { 1042 | continue; 1043 | } 1044 | input.GetFlagSet().UnsetFlag(PortFlag::Hovered | PortFlag::Connectible | PortFlag::Draging); 1045 | 1046 | if (state == State::DragingInput) 1047 | { 1048 | if (iNodeInput == &input) 1049 | { 1050 | input.GetFlagSet().SetFlag(PortFlag::Draging); 1051 | } 1052 | continue; 1053 | } 1054 | 1055 | if (state == State::DragingOutput) 1056 | { 1057 | if (iNode == node) // Not allowed to connect itself. 1058 | { 1059 | continue; 1060 | } 1061 | if (ConnectionRules(node, iNode, &input, iNodeOutput)) 1062 | { 1063 | input.GetFlagSet().SetFlag(PortFlag::Connectible); 1064 | } 1065 | } 1066 | 1067 | // If node is not hovNode. 1068 | if (hovNode != node) 1069 | { 1070 | continue; 1071 | } 1072 | // If the state is selecting. 1073 | if (state == State::Selecting) 1074 | { 1075 | continue; 1076 | } 1077 | // If input belongs to a selected node. However, it is connectible from an output. 1078 | if (state != State::DragingOutput && node->GetFlagSet().HasFlag(NodeFlag::Selected)) 1079 | { 1080 | continue; 1081 | } 1082 | 1083 | // Hovered input condition. 1084 | ImRect inputRect = input.GetRectPort(); 1085 | inputRect.Min *= scale; 1086 | inputRect.Max *= scale; 1087 | ImVec2 offset = position + scroll; 1088 | inputRect.Translate(offset); 1089 | if (state != State::None && inputRect.Contains(mousePos)) 1090 | { 1091 | if (state != State::DragingOutput) 1092 | { 1093 | input.GetFlagSet().SetFlag(PortFlag::Hovered); 1094 | continue; 1095 | } 1096 | if (input.GetFlagSet().HasFlag(PortFlag::Connectible)) 1097 | { 1098 | input.GetFlagSet().SetFlag(PortFlag::Hovered); 1099 | } 1100 | } 1101 | } 1102 | } 1103 | 1104 | void CoreDiagram::UpdateOutputFlags(CoreNode* node) 1105 | { 1106 | for (auto& output : node->GetOutputVec()) 1107 | { 1108 | if (output.GetType() == PortType::None) 1109 | { 1110 | continue; 1111 | } 1112 | output.GetFlagSet().UnsetFlag(PortFlag::Hovered | PortFlag::Connectible | PortFlag::Draging); 1113 | 1114 | if (state == State::DragingOutput) 1115 | { 1116 | if (iNodeOutput == &output) 1117 | { 1118 | output.GetFlagSet().SetFlag(PortFlag::Draging); 1119 | } 1120 | continue; 1121 | } 1122 | 1123 | if (state == State::DragingInput) 1124 | { 1125 | if (iNode == node) // Not allowed to connect itself. 1126 | { 1127 | continue; 1128 | } 1129 | if (ConnectionRules(iNode, node, iNodeInput, &output)) 1130 | { 1131 | output.GetFlagSet().SetFlag(PortFlag::Connectible); 1132 | } 1133 | } 1134 | 1135 | // If node is not hovNode. 1136 | if (hovNode != node) 1137 | { 1138 | continue; 1139 | } 1140 | // If the state is selecting. 1141 | if (state == State::Selecting) 1142 | { 1143 | continue; 1144 | } 1145 | // If output belongs to a selected node. However, it is connectible from an input. 1146 | if (state != State::DragingInput && node->GetFlagSet().HasFlag(NodeFlag::Selected)) 1147 | { 1148 | continue; 1149 | } 1150 | 1151 | // Hovered output condition. 1152 | ImRect outputRect = output.GetRectPort(); 1153 | outputRect.Min *= scale; 1154 | outputRect.Max *= scale; 1155 | ImVec2 offset = position + scroll; 1156 | outputRect.Translate(offset); 1157 | if (state != State::None && outputRect.Contains(mousePos)) 1158 | { 1159 | if (state != State::DragingInput) 1160 | { 1161 | output.GetFlagSet().SetFlag(PortFlag::Hovered); 1162 | continue; 1163 | } 1164 | if (output.GetFlagSet().HasFlag(PortFlag::Connectible)) 1165 | { 1166 | output.GetFlagSet().SetFlag(PortFlag::Hovered); 1167 | } 1168 | } 1169 | } 1170 | } 1171 | 1172 | void CoreDiagram::HighlightNode() 1173 | { 1174 | if (highlightedNode != nullptr) // Unset highlighted node. 1175 | { 1176 | highlightedNode->GetFlagSet().UnsetFlag(NodeFlag::Highlighted); 1177 | } 1178 | iNode->GetFlagSet().SetFlag(NodeFlag::Highlighted); 1179 | if (iNode != highlightedNode) 1180 | { 1181 | modifFlag = true; 1182 | } 1183 | highlightedNode = iNode; // Set highlighted node. 1184 | if (coreNodeVec.back() != iNode) 1185 | { 1186 | coreNodeVec.erase(std::find(coreNodeVec.begin(), coreNodeVec.end(), iNode)); 1187 | coreNodeVec.push_back(iNode); // Bring iNode to front. 1188 | } 1189 | } 1190 | 1191 | std::string CoreDiagram::CreateUniqueName(const std::string& libName) const 1192 | { 1193 | std::string name = libName; 1194 | unsigned int i = 0; 1195 | bool nameFlag = true; 1196 | while (nameFlag == true) 1197 | { 1198 | nameFlag = false; 1199 | for (auto n : coreNodeVec) 1200 | { 1201 | if (n->GetName() == name) 1202 | { 1203 | nameFlag = true; 1204 | if (i != 0) 1205 | { 1206 | for (int k = 0; k < std::to_string(i).length(); k++) 1207 | { 1208 | name.pop_back(); 1209 | } 1210 | } 1211 | i += 1; 1212 | name += std::to_string(i); 1213 | } 1214 | } 1215 | } 1216 | return name; 1217 | } 1218 | 1219 | bool CoreDiagram::ConnectionRules([[maybe_unused]] const CoreNode* inputNode, const CoreNode* outputNode, const CoreNodeInput* input, const CoreNodeOutput* output) const 1220 | { 1221 | if (input->GetTargetNode() != nullptr && input->GetTargetNode() == outputNode) // If there is already a connection. 1222 | { 1223 | return false; 1224 | } 1225 | if (input->GetDataType() == output->GetDataType()) 1226 | { 1227 | return true; 1228 | } 1229 | if (input->GetDataType() == PortDataType::Generic) 1230 | { 1231 | return true; 1232 | } 1233 | if (output->GetDataType() == PortDataType::Generic) 1234 | { 1235 | return true; 1236 | } 1237 | return false; 1238 | } 1239 | 1240 | void CoreDiagram::SortNodeOrder() 1241 | { 1242 | std::vector unselectedNodes; 1243 | std::vector selectedNodes; 1244 | unselectedNodes.reserve(coreNodeVec.size()); 1245 | selectedNodes.reserve(coreNodeVec.size()); 1246 | for (const auto& node : coreNodeVec) 1247 | { 1248 | if (node->GetFlagSet().HasFlag(NodeFlag::Selected)) 1249 | { 1250 | selectedNodes.push_back(node); 1251 | } 1252 | else 1253 | { 1254 | unselectedNodes.push_back(node); 1255 | } 1256 | } 1257 | int i = 0; 1258 | for (const auto& node : unselectedNodes) 1259 | { 1260 | coreNodeVec[i] = node; 1261 | i++; 1262 | } 1263 | for (const auto& node : selectedNodes) 1264 | { 1265 | coreNodeVec[i] = node; 1266 | i++; 1267 | } 1268 | } 1269 | 1270 | void CoreDiagram::PopupMenu() 1271 | { 1272 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); 1273 | if (ImGui::BeginPopup("DiagramPopupMenu")) 1274 | { 1275 | if (ImGui::MenuItem("Fit to window")) 1276 | { 1277 | FitToWindow(); 1278 | } 1279 | ImGui::EndPopup(); 1280 | } 1281 | ImGui::PopStyleVar(); 1282 | } 1283 | 1284 | void CoreDiagram::EraseLink(const CoreNodeInput* input) 1285 | { 1286 | auto it = linkVec.begin(); 1287 | while (it != linkVec.end()) 1288 | { 1289 | if (it->inputPort == input) 1290 | { 1291 | it = linkVec.erase(it); 1292 | return; 1293 | } 1294 | else 1295 | { 1296 | ++it; 1297 | } 1298 | } 1299 | } 1300 | 1301 | bool CoreDiagram::IsLinkVisible(ImVec2 pInput, ImVec2 pOutput) const 1302 | { 1303 | if (rectCanvas.Contains(pInput) || rectCanvas.Contains(pOutput)) 1304 | { 1305 | return true; 1306 | } 1307 | return false; 1308 | } 1309 | 1310 | void CoreDiagram::DrawLinks() const 1311 | { 1312 | for (const auto& link : linkVec) 1313 | { 1314 | const ImVec2 offset = position + scroll; 1315 | ImVec2 pInput = link.inputPort->GetPosition() * scale + offset; 1316 | ImVec2 pOutput = link.outputPort->GetPosition() * scale + offset; 1317 | 1318 | if (link.inputNode->GetFlagSet().HasFlag(NodeFlag::Collapsed)) 1319 | { 1320 | float midPointY = (link.inputNode->GetRectNode().Max.y - link.inputNode->GetRectNode().Min.y) * 0.5f; 1321 | ImVec2 collapsedPoint; 1322 | if (link.inputNode->IsPortInverted() == false) 1323 | { 1324 | collapsedPoint = link.inputNode->GetRectNode().Min + ImVec2(0, midPointY); 1325 | } 1326 | else 1327 | { 1328 | collapsedPoint = link.inputNode->GetRectNode().Max - ImVec2(0, midPointY); 1329 | } 1330 | pInput = collapsedPoint * scale + offset; 1331 | } 1332 | if (link.outputNode->GetFlagSet().HasFlag(NodeFlag::Collapsed)) 1333 | { 1334 | float midPointY = (link.outputNode->GetRectNode().Max.y - link.outputNode->GetRectNode().Min.y) * 0.5f; 1335 | ImVec2 collapsedPoint; 1336 | if (link.outputNode->IsPortInverted() == false) 1337 | { 1338 | collapsedPoint = link.outputNode->GetRectNode().Max - ImVec2(0, midPointY); 1339 | } 1340 | else 1341 | { 1342 | collapsedPoint = link.outputNode->GetRectNode().Min + ImVec2(0, midPointY); 1343 | } 1344 | pOutput = collapsedPoint * scale + offset; 1345 | } 1346 | if (IsLinkVisible(pInput, pOutput) == false) 1347 | { 1348 | continue; 1349 | } 1350 | 1351 | float linkDistance = std::sqrt((pInput.x - pOutput.x) * (pInput.x - pOutput.x) + (pInput.y - pOutput.y) * (pInput.y - pOutput.y)) / 150.0f; 1352 | float rounding = 25.0f * linkDistance / scale; 1353 | float dHandle = 15.0f * scale; 1354 | 1355 | if (link.type == LinkType::BINV_LEFT) 1356 | { 1357 | DrawLinkBezier(link, pInput, pOutput, rounding, true); 1358 | } 1359 | else if (link.type == LinkType::BINV_RIGHT_OVER || link.type == LinkType::BINV_RIGHT_UNDER || link.type == LinkType::BINV_RIGHT_MID) 1360 | { 1361 | DrawLinkBNInv(link, pInput, pOutput, dHandle, true); 1362 | } 1363 | else if (link.type == LinkType::IINV_RIGHT_OVER || link.type == LinkType::IINV_LEFT_OVER || 1364 | link.type == LinkType::IINV_RIGHT_UNDER || link.type == LinkType::IINV_LEFT_UNDER || 1365 | link.type == LinkType::OINV_RIGHT_OVER || link.type == LinkType::OINV_LEFT_OVER || 1366 | link.type == LinkType::OINV_RIGHT_UNDER || link.type == LinkType::OINV_LEFT_UNDER) 1367 | { 1368 | DrawLinkIOInv(link, pInput, pOutput, dHandle); 1369 | } 1370 | else if (link.type == LinkType::IINV_MID || link.type == LinkType::OINV_MID) 1371 | { 1372 | DrawLinkBezier(link, pInput, pOutput, 0.0f); 1373 | } 1374 | else if (link.type == LinkType::NINV_RIGHT) 1375 | { 1376 | DrawLinkBezier(link, pInput, pOutput, rounding); 1377 | } 1378 | else if (link.type == LinkType::NINV_LEFT_OVER || link.type == LinkType::NINV_LEFT_UNDER || link.type == LinkType::NINV_LEFT_MID) 1379 | { 1380 | DrawLinkBNInv(link, pInput, pOutput, dHandle); 1381 | } 1382 | } 1383 | } 1384 | 1385 | void CoreDiagram::SetLinkProperties() 1386 | { 1387 | for (auto& link : linkVec) 1388 | { 1389 | link.inputPort->SetLinkDir(0); 1390 | link.inputPort->SetLinkSepX(0); 1391 | link.inputPort->SetLinkSepY(0); 1392 | link.inputPort->SetTargetLinkDir(0); 1393 | link.inputPort->SetTargetLinkSep(0); 1394 | } 1395 | for (auto& link : linkVec) 1396 | { 1397 | link.type = LinkType::NONE; 1398 | link.color = ImColor(1.0f, 1.0f, 1.0f, 1.0f); 1399 | link.thickness = 2.0f * scale; 1400 | link.xSepIn = link.inputPort->GetRectPort().GetHeight() * scale; 1401 | link.xSepOut = link.inputPort->GetRectPort().GetHeight() * scale; 1402 | link.ykSep = 0; 1403 | const ImVec2 offset = position + scroll; 1404 | auto rInput = ImRect(link.inputNode->GetRectNode().Min * scale + offset, link.inputNode->GetRectNode().Max * scale + offset); 1405 | auto rOutput = ImRect(link.outputNode->GetRectNode().Min * scale + offset, link.outputNode->GetRectNode().Max * scale + offset); 1406 | float yInput = (link.inputPort->GetRectPin().GetCenter() * scale + offset).y; 1407 | float yOutput = (link.inputPort->GetTargetNodeOutput()->GetRectPin().GetCenter() * scale + offset).y; 1408 | float yMargin = 30.0f * scale; 1409 | bool inputNodeInverted = link.inputNode->IsPortInverted(); 1410 | bool outputNodeInverted = link.outputNode->IsPortInverted(); 1411 | if (inputNodeInverted && outputNodeInverted) 1412 | { 1413 | if (rInput.Max.x <= rOutput.Min.x) 1414 | { 1415 | link.type = LinkType::BINV_LEFT; 1416 | } 1417 | if (rInput.Max.x > rOutput.Min.x) 1418 | { 1419 | float nodeMargin = 24.0f * scale; 1420 | if (rInput.Max.y + nodeMargin < rOutput.Min.y) 1421 | { 1422 | link.type = LinkType::BINV_RIGHT_OVER; 1423 | SetOutputSepUp(link); 1424 | SetInputSepDown(link); 1425 | SetNodeSep(link); 1426 | } 1427 | else if (rInput.Min.y > rOutput.Max.y + nodeMargin) 1428 | { 1429 | link.type = LinkType::BINV_RIGHT_UNDER; 1430 | SetOutputSepDown(link); 1431 | SetInputSepUp(link); 1432 | SetNodeSep(link); 1433 | } 1434 | else 1435 | { 1436 | link.type = LinkType::BINV_RIGHT_MID; 1437 | SetOutputSepDown(link); 1438 | SetInputSepDown(link); 1439 | SetNodeSep(link); 1440 | } 1441 | } 1442 | } 1443 | else if (inputNodeInverted) 1444 | { 1445 | if (yInput + yMargin < yOutput) 1446 | { 1447 | if (ImMax(rInput.Max.x, rOutput.Max.x) == rInput.Max.x) 1448 | { 1449 | link.type = LinkType::IINV_RIGHT_OVER; 1450 | SetInputSepDown(link); 1451 | } 1452 | else if (ImMax(rInput.Max.x, rOutput.Max.x) == rOutput.Max.x) 1453 | { 1454 | link.type = LinkType::IINV_LEFT_OVER; 1455 | SetOutputSepUp(link); 1456 | } 1457 | } 1458 | else if (yInput > yOutput + yMargin) 1459 | { 1460 | if (ImMax(rInput.Max.x, rOutput.Max.x) == rInput.Max.x) 1461 | { 1462 | link.type = LinkType::IINV_RIGHT_UNDER; 1463 | SetInputSepUp(link); 1464 | } 1465 | else if (ImMax(rInput.Max.x, rOutput.Max.x) == rOutput.Max.x) 1466 | { 1467 | link.type = LinkType::IINV_LEFT_UNDER; 1468 | SetOutputSepDown(link); 1469 | } 1470 | } 1471 | else 1472 | { 1473 | link.type = LinkType::IINV_MID; 1474 | } 1475 | } 1476 | else if (outputNodeInverted) 1477 | { 1478 | if (yInput + yMargin < yOutput) 1479 | { 1480 | if (ImMax(rInput.Min.x, rOutput.Min.x) == rInput.Min.x) 1481 | { 1482 | link.type = LinkType::OINV_RIGHT_OVER; 1483 | SetOutputSepUp(link); 1484 | } 1485 | else if (ImMax(rInput.Min.x, rOutput.Min.x) == rOutput.Min.x) 1486 | { 1487 | link.type = LinkType::OINV_LEFT_OVER; 1488 | SetInputSepDown(link); 1489 | } 1490 | } 1491 | else if (yInput > yOutput + yMargin) 1492 | { 1493 | if (ImMax(rInput.Min.x, rOutput.Min.x) == rInput.Min.x) 1494 | { 1495 | link.type = LinkType::OINV_RIGHT_UNDER; 1496 | SetOutputSepDown(link); 1497 | } 1498 | else if (ImMax(rInput.Min.x, rOutput.Min.x) == rOutput.Min.x) 1499 | { 1500 | link.type = LinkType::OINV_LEFT_UNDER; 1501 | SetInputSepUp(link); 1502 | } 1503 | } 1504 | else 1505 | { 1506 | link.type = LinkType::OINV_MID; 1507 | } 1508 | } 1509 | else if (rInput.Min.x >= rOutput.Max.x) 1510 | { 1511 | link.type = LinkType::NINV_RIGHT; 1512 | } 1513 | else if (rInput.Min.x < rOutput.Max.x) 1514 | { 1515 | float nodeMargin = 24.0f * scale; 1516 | if (rInput.Max.y + nodeMargin < rOutput.Min.y) 1517 | { 1518 | link.type = LinkType::NINV_LEFT_OVER; 1519 | SetInputSepDown(link); 1520 | SetOutputSepUp(link); 1521 | SetNodeSep(link); 1522 | } 1523 | else if (rInput.Min.y > rOutput.Max.y + nodeMargin) 1524 | { 1525 | link.type = LinkType::NINV_LEFT_UNDER; 1526 | SetInputSepUp(link); 1527 | SetOutputSepDown(link); 1528 | SetNodeSep(link); 1529 | } 1530 | else 1531 | { 1532 | link.type = LinkType::NINV_LEFT_MID; 1533 | SetOutputSepDown(link); 1534 | SetInputSepDown(link); 1535 | SetNodeSep(link); 1536 | } 1537 | } 1538 | } 1539 | } 1540 | 1541 | void CoreDiagram::DrawFreeLink() const 1542 | { 1543 | if (inputFreeLink.x != outputFreeLink.x || inputFreeLink.y != outputFreeLink.y) 1544 | { 1545 | Link link; 1546 | link.color = ImColor(0.996f, 0.431f, 0.000f, 1.0f); 1547 | link.thickness = 2.0f * scale; 1548 | DrawLinkBezier(link, inputFreeLink, outputFreeLink, 0.0f); 1549 | } 1550 | } 1551 | 1552 | void CoreDiagram::DrawLinkBezier(const Link& link, ImVec2 pInput, ImVec2 pOutput, float rounding, bool invert) const 1553 | { 1554 | if (IsLinkVisible(pInput, pOutput) == true) 1555 | { 1556 | ImDrawList* drawList = ImGui::GetWindowDrawList(); 1557 | ImVec2 handle = invert ? ImVec2(rounding, 0.0f) * scale : ImVec2(-rounding, 0.0f) * scale; 1558 | ImVec2 p1 = pInput; 1559 | ImVec2 p2 = pInput + handle; 1560 | ImVec2 p3 = pOutput - handle; 1561 | ImVec2 p4 = pOutput; 1562 | drawList->AddBezierCurve(p1, p2, p3, p4, link.color, link.thickness); 1563 | } 1564 | } 1565 | 1566 | void CoreDiagram::DrawLinkIOInv(const Link& link, ImVec2 pInput, ImVec2 pOutput, float dHandle) const 1567 | { 1568 | float xMax = ImMax(pInput.x, pOutput.x); 1569 | float xMin = ImMin(pInput.x, pOutput.x); 1570 | float xMargin = dHandle; 1571 | 1572 | float x1 = 0.0f; 1573 | float x2 = 0.0f; 1574 | // Input inverted. 1575 | if (link.type == LinkType::IINV_LEFT_OVER || link.type == LinkType::IINV_LEFT_UNDER) 1576 | { 1577 | x1 = xMax - xMargin + link.xSepOut; 1578 | x2 = x1 + dHandle; 1579 | } 1580 | if (link.type == LinkType::IINV_RIGHT_OVER || link.type == LinkType::IINV_RIGHT_UNDER) 1581 | { 1582 | x1 = xMax - xMargin + link.xSepIn; 1583 | x2 = x1 + dHandle; 1584 | } 1585 | // Output inverted. 1586 | if (link.type == LinkType::OINV_LEFT_OVER || link.type == LinkType::OINV_LEFT_UNDER) 1587 | { 1588 | x1 = xMin + xMargin - link.xSepIn; 1589 | x2 = x1 - dHandle; 1590 | } 1591 | if (link.type == LinkType::OINV_RIGHT_OVER || link.type == LinkType::OINV_RIGHT_UNDER) 1592 | { 1593 | x1 = xMin + xMargin - link.xSepOut; 1594 | x2 = x1 - dHandle; 1595 | } 1596 | // Input is over the output. 1597 | if (link.type == LinkType::IINV_LEFT_OVER || link.type == LinkType::IINV_RIGHT_OVER || 1598 | link.type == LinkType::OINV_LEFT_OVER || link.type == LinkType::OINV_RIGHT_OVER) 1599 | { 1600 | dHandle *= +1; 1601 | } 1602 | // Input is under the output. 1603 | if (link.type == LinkType::IINV_LEFT_UNDER || link.type == LinkType::IINV_RIGHT_UNDER || 1604 | link.type == LinkType::OINV_LEFT_UNDER || link.type == LinkType::OINV_RIGHT_UNDER) 1605 | { 1606 | dHandle *= -1; 1607 | } 1608 | 1609 | float y1 = pInput.y + dHandle; 1610 | float y2 = pOutput.y - dHandle; 1611 | 1612 | std::vector points; 1613 | points.reserve(8); 1614 | points.push_back(pInput); 1615 | points.push_back(ImVec2(x1, pInput.y)); 1616 | points.push_back(ImVec2(x2, pInput.y)); 1617 | points.push_back(ImVec2(x2, y1)); 1618 | points.push_back(ImVec2(x2, y2)); 1619 | points.push_back(ImVec2(x2, pOutput.y)); 1620 | points.push_back(ImVec2(x1, pOutput.y)); 1621 | points.push_back(pOutput); 1622 | 1623 | ImDrawList* drawList = ImGui::GetWindowDrawList(); 1624 | drawList->AddBezierQuadratic(points[0], (points[0] + points[1]) * 0.5f, points[1], link.color, link.thickness); 1625 | drawList->AddBezierQuadratic(points[1], points[2], points[3], link.color, link.thickness); 1626 | drawList->AddBezierQuadratic(points[3], (points[3] + points[4]) * 0.5f, points[4], link.color, link.thickness); 1627 | drawList->AddBezierQuadratic(points[4], points[5], points[6], link.color, link.thickness); 1628 | drawList->AddBezierQuadratic(points[6], (points[6] + points[7]) * 0.5f, points[7], link.color, link.thickness); 1629 | } 1630 | 1631 | void CoreDiagram::DrawLinkBNInv(const Link& link, ImVec2 pInput, ImVec2 pOutput, float dHandle, bool invert) const 1632 | { 1633 | const ImVec2 offset = position + scroll; 1634 | auto rInputNode = ImRect(link.inputNode->GetRectNode().Min * scale + offset, link.inputNode->GetRectNode().Max * scale + offset); 1635 | auto rOutputNode = ImRect(link.outputNode->GetRectNode().Min * scale + offset, link.outputNode->GetRectNode().Max * scale + offset); 1636 | 1637 | // Unique lines between two nodes. 1638 | std::vector vec; 1639 | for (const auto& input : link.inputNode->GetInputVec()) 1640 | { 1641 | if (input.GetTargetNode() == link.outputNode) 1642 | { 1643 | vec.push_back(input.GetTargetNodeOutput()); 1644 | } 1645 | } 1646 | auto numberOfUniqueLines = (int)std::set(vec.begin(), vec.end()).size(); 1647 | 1648 | float x1 = 0; 1649 | float x2 = 0; 1650 | float x3 = 0; 1651 | float x4 = 0; 1652 | float xMargin = dHandle; 1653 | if (invert == false) 1654 | { 1655 | x1 = pInput.x + xMargin - link.xSepIn; 1656 | x2 = x1 - dHandle; 1657 | x3 = pOutput.x - xMargin + link.xSepOut; 1658 | x4 = x3 + dHandle; 1659 | } 1660 | else 1661 | { 1662 | x1 = pInput.x - xMargin + link.xSepIn; 1663 | x2 = x1 + dHandle; 1664 | x3 = pOutput.x + xMargin - link.xSepOut; 1665 | x4 = x3 - dHandle; 1666 | } 1667 | 1668 | float yM = 0; 1669 | float y1 = 0; 1670 | float y2 = 0; 1671 | float y3 = 0; 1672 | float y4 = 0; 1673 | float y5 = 0; 1674 | if (link.type == LinkType::NINV_LEFT_OVER || link.type == LinkType::BINV_RIGHT_OVER) 1675 | { 1676 | dHandle *= +1; 1677 | yM = rInputNode.Max.y + link.ykSep * (rOutputNode.Min.y - rInputNode.Max.y) / float(numberOfUniqueLines + 1); 1678 | y1 = pInput.y + dHandle; 1679 | y2 = yM - dHandle; 1680 | y3 = y2 + dHandle; 1681 | y4 = y3 + dHandle; 1682 | y5 = pOutput.y - dHandle; 1683 | } 1684 | else if (link.type == LinkType::NINV_LEFT_UNDER || link.type == LinkType::BINV_RIGHT_UNDER) 1685 | { 1686 | dHandle *= -1; 1687 | yM = rOutputNode.Max.y + link.ykSep * (rInputNode.Min.y - rOutputNode.Max.y) / float(numberOfUniqueLines + 1); 1688 | y1 = pInput.y + dHandle; 1689 | y2 = yM - dHandle; 1690 | y3 = y2 + dHandle; 1691 | y4 = y3 + dHandle; 1692 | y5 = pOutput.y - dHandle; 1693 | } 1694 | else if (link.type == LinkType::NINV_LEFT_MID || link.type == LinkType::BINV_RIGHT_MID) 1695 | { 1696 | float yMax = ImMax(rInputNode.Max.y, rOutputNode.Max.y); 1697 | float yMargin = 4.0f * scale; 1698 | yM = yMax + (yMargin + dHandle) * link.ykSep; 1699 | 1700 | y1 = pInput.y + dHandle; 1701 | y2 = yM - dHandle; 1702 | y3 = y2 + dHandle; 1703 | y4 = y3 - dHandle; 1704 | y5 = pOutput.y + dHandle; 1705 | } 1706 | 1707 | std::vector points; 1708 | points.reserve(14); 1709 | points.push_back(pInput); 1710 | points.push_back(ImVec2(x1, pInput.y)); 1711 | points.push_back(ImVec2(x2, pInput.y)); 1712 | points.push_back(ImVec2(x2, y1)); 1713 | points.push_back(ImVec2(x2, y2)); 1714 | points.push_back(ImVec2(x2, y3)); 1715 | points.push_back(ImVec2(x1, y3)); 1716 | points.push_back(ImVec2(x3, y3)); 1717 | points.push_back(ImVec2(x4, y3)); 1718 | points.push_back(ImVec2(x4, y4)); 1719 | points.push_back(ImVec2(x4, y5)); 1720 | points.push_back(ImVec2(x4, pOutput.y)); 1721 | points.push_back(ImVec2(x3, pOutput.y)); 1722 | points.push_back(pOutput); 1723 | 1724 | ImDrawList* drawList = ImGui::GetWindowDrawList(); 1725 | drawList->AddBezierQuadratic(points[0], (points[0] + points[1]) * 0.5f, points[1], link.color, link.thickness); 1726 | drawList->AddBezierQuadratic(points[1], points[2], points[3], link.color, link.thickness); 1727 | drawList->AddBezierQuadratic(points[3], (points[3] + points[4]) * 0.5f, points[4], link.color, link.thickness); 1728 | drawList->AddBezierQuadratic(points[4], points[5], points[6], link.color, link.thickness); 1729 | drawList->AddBezierQuadratic(points[6], (points[6] + points[7]) * 0.5f, points[7], link.color, link.thickness); 1730 | drawList->AddBezierQuadratic(points[7], points[8], points[9], link.color, link.thickness); 1731 | drawList->AddBezierQuadratic(points[9], (points[9] + points[10]) * 0.5f, points[10], link.color, link.thickness); 1732 | drawList->AddBezierQuadratic(points[10], points[11], points[12], link.color, link.thickness); 1733 | drawList->AddBezierQuadratic(points[12], (points[12] + points[13]) * 0.5f, points[13], link.color, link.thickness); 1734 | } 1735 | 1736 | void CoreDiagram::SetInputSepDown(Link& link) const 1737 | { 1738 | int kSep = 1; 1739 | while (true) 1740 | { 1741 | bool full = false; 1742 | for (const auto& input : link.inputNode->GetInputVec()) 1743 | { 1744 | if (input.GetLinkSepX() == kSep && 1745 | (input.GetOrder() < link.inputPort->GetOrder() && input.GetLinkDir() == -1) == false && 1746 | input.GetTargetNodeOutput() != link.outputPort) // If it is the same output, no need to seperate. 1747 | { 1748 | full = true; 1749 | kSep += 1; 1750 | break; 1751 | } 1752 | } 1753 | if (full == false) 1754 | { 1755 | link.inputPort->SetLinkDir(1); 1756 | link.inputPort->SetLinkSepX(kSep); 1757 | link.xSepIn *= (float)kSep; 1758 | break; 1759 | } 1760 | } 1761 | } 1762 | 1763 | void CoreDiagram::SetInputSepUp(Link& link) const 1764 | { 1765 | int kSep = 1; 1766 | while (true) 1767 | { 1768 | bool full = false; 1769 | for (const auto& input : link.inputNode->GetInputVec()) 1770 | { 1771 | if (input.GetLinkSepX() == kSep && 1772 | (input.GetOrder() > link.inputPort->GetOrder() && input.GetLinkDir() == 1) == false && 1773 | input.GetTargetNodeOutput() != link.outputPort) // If it is the same output, no need to seperate. 1774 | { 1775 | full = true; 1776 | kSep += 1; 1777 | break; 1778 | } 1779 | } 1780 | if (full == false) 1781 | { 1782 | link.inputPort->SetLinkDir(-1); 1783 | link.inputPort->SetLinkSepX(kSep); 1784 | link.xSepIn *= (float)kSep; 1785 | break; 1786 | } 1787 | } 1788 | } 1789 | 1790 | void CoreDiagram::SetOutputSepUp(Link& link) const 1791 | { 1792 | // Collect all inputs related with the output node. 1793 | std::vector inputVec; 1794 | for (const auto& node : coreNodeVec) 1795 | { 1796 | for (auto& input : node->GetInputVec()) 1797 | { 1798 | if (input.GetTargetNode() == link.outputNode) 1799 | { 1800 | inputVec.push_back(&input); 1801 | } 1802 | } 1803 | } 1804 | 1805 | int kSep = 1; 1806 | while (true) 1807 | { 1808 | bool full = false; 1809 | for (const auto& input : inputVec) 1810 | { 1811 | if (input->GetTargetLinkSep() == kSep && 1812 | (input->GetTargetNodeOutput()->GetOrder() >= link.outputPort->GetOrder() && input->GetTargetLinkDir() == 1) == false && 1813 | input->GetTargetNodeOutput() != link.outputPort) // If it is the same output, no need to seperate. 1814 | { 1815 | full = true; 1816 | kSep += 1; 1817 | break; 1818 | } 1819 | } 1820 | if (full == false) 1821 | { 1822 | link.inputPort->SetTargetLinkDir(-1); 1823 | link.inputPort->SetTargetLinkSep(kSep); 1824 | link.xSepOut *= (float)kSep; 1825 | break; 1826 | } 1827 | } 1828 | } 1829 | 1830 | void CoreDiagram::SetOutputSepDown(Link& link) const 1831 | { 1832 | // Collect all inputs related with the output node. 1833 | std::vector inputVec; 1834 | for (const auto& node : coreNodeVec) 1835 | { 1836 | for (auto& input : node->GetInputVec()) 1837 | { 1838 | if (input.GetTargetNode() == link.outputNode) 1839 | { 1840 | inputVec.push_back(&input); 1841 | } 1842 | } 1843 | } 1844 | 1845 | int kSep = 1; 1846 | while (true) 1847 | { 1848 | bool full = false; 1849 | for (const auto& input : inputVec) 1850 | { 1851 | if (input->GetTargetNode() == link.outputNode && input->GetTargetLinkSep() == kSep && 1852 | (input->GetTargetNodeOutput()->GetOrder() <= link.outputPort->GetOrder() && input->GetTargetLinkDir() == -1) == false && 1853 | input->GetTargetNodeOutput() != link.outputPort) // If it is the same output, no need to seperate. 1854 | { 1855 | full = true; 1856 | kSep += 1; 1857 | break; 1858 | } 1859 | } 1860 | if (full == false) 1861 | { 1862 | link.inputPort->SetTargetLinkDir(1); 1863 | link.inputPort->SetTargetLinkSep(kSep); 1864 | link.xSepOut *= (float)kSep; 1865 | break; 1866 | } 1867 | } 1868 | } 1869 | 1870 | void CoreDiagram::SetNodeSep(Link& link) const 1871 | { 1872 | int kSep = 1; 1873 | while (true) 1874 | { 1875 | bool full = false; 1876 | for (const auto& input : link.inputNode->GetInputVec()) 1877 | { 1878 | if (input.GetLinkSepY() == kSep && 1879 | input.GetTargetNodeOutput() != link.outputPort) // If it is the same output, no need to seperate. 1880 | { 1881 | full = true; 1882 | kSep += 1; 1883 | break; 1884 | } 1885 | } 1886 | if (full == false) 1887 | { 1888 | link.inputPort->SetLinkSepY(kSep); 1889 | link.ykSep = float(kSep); 1890 | break; 1891 | } 1892 | } 1893 | } -------------------------------------------------------------------------------- /core-nodes/CoreDiagram.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Diagram * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef COREDIAGRAM_HPP 11 | #define COREDIAGRAM_HPP 12 | 13 | #include "CoreLibrary.hpp" 14 | #include 15 | #include 16 | 17 | enum class State 18 | { 19 | None = 0, 20 | Default, 21 | HoveringNode, 22 | HoveringInput, 23 | HoveringOutput, 24 | Draging, 25 | DragingInput, 26 | DragingOutput, 27 | Selecting 28 | }; 29 | 30 | class CoreDiagram 31 | { 32 | private: 33 | bool mNodeDrag = false; // For node drag modification. 34 | bool modifFlag = false; // Modification flag. 35 | 36 | CoreLibrary coreLib; 37 | State state = State::Default; 38 | float scale = 1.0f; 39 | const float scaleMin = 0.10f; 40 | const float scaleMax = 2.0f; 41 | ImVec2 position; 42 | ImVec2 size; 43 | ImVec2 scroll; 44 | ImVec2 mousePos; 45 | std::vector coreNodeVec; 46 | std::vector exeOrder; // Execution order. 47 | CoreNode* highlightedNode = nullptr; 48 | void HighlightNode(); 49 | std::string CreateUniqueName(const std::string& libName) const; 50 | 51 | // Update canvas 52 | ImRect rectCanvas; 53 | void UpdateCanvasRect(); 54 | const float deltaScale = 0.05f; 55 | void UpdateCanvasScrollZoom(); 56 | void UpdateCanvasGrid(ImDrawList* drawList) const; 57 | void FitToWindow(); 58 | 59 | // Dragging on canvas. 60 | bool draggingOutOfCanvas = false; 61 | ImVec2 distFromClickToCenter; 62 | ImVec2 clickPosAtTheEdge; 63 | void DragNodeSingle(); 64 | void DragNodeMulti(); 65 | 66 | // Actions 67 | void Actions(); 68 | void MouseMove(); 69 | void MouseLeftButtonDoubleClick(); 70 | void MouseRightButtonDoubleClick(); 71 | void MouseLeftButtonSingleClick(); 72 | void MouseRightButtonSingleClick(); 73 | void MouseLeftButtonDrag(); 74 | void MouseLeftButtonRelease(); 75 | void MouseRightButtonRelease(); 76 | void KeyboardPressDelete(); 77 | 78 | // Draw canvas elements 79 | void DrawCanvasElements(); 80 | void DrawNodes() const; 81 | ImRect rectSelecting; 82 | void DrawSelecting() const; 83 | void PrintInfo(); 84 | 85 | // Interaction 86 | CoreNode* hovNode = nullptr; // Hovered node 87 | CoreNode* iNode = nullptr; // Node under interaction 88 | CoreNodeInput* iNodeInput = nullptr; // Node input under interaction 89 | CoreNodeOutput* iNodeOutput = nullptr; // Node output under interaction 90 | void UpdateNodeFlags(); 91 | void UpdateInputFlags(CoreNode* node); 92 | void UpdateOutputFlags(CoreNode* node); 93 | bool ConnectionRules([[maybe_unused]] const CoreNode* inputNode, const CoreNode* outputNode, const CoreNodeInput* input, const CoreNodeOutput* output) const; 94 | void SortNodeOrder(); 95 | void PopupMenu(); 96 | 97 | // Network 98 | enum class LinkType; 99 | struct Link 100 | { 101 | CoreNode* inputNode; 102 | CoreNode* outputNode; 103 | CoreNodeInput* inputPort; 104 | CoreNodeOutput* outputPort; 105 | LinkType type; 106 | ImColor color; 107 | float thickness; 108 | float xSepIn; 109 | float xSepOut; 110 | float ykSep = 0; 111 | void Save(pugi::xml_node& xmlNode) const 112 | { 113 | auto link = xmlNode.append_child("link"); 114 | SaveString(link, "inputNode", inputNode->GetName()); 115 | SaveString(link, "outputNode", outputNode->GetName()); 116 | SaveInt(link, "inputPort", inputPort->GetOrder()); 117 | SaveInt(link, "outputPort", outputPort->GetOrder()); 118 | SaveInt(link, "type", (int)type); 119 | SaveImColor(link, "color", color); 120 | SaveFloat(link, "thickness", thickness); 121 | SaveFloat(link, "xSepIn", xSepIn); 122 | SaveFloat(link, "xSepOut", xSepOut); 123 | SaveFloat(link, "ykSep", ykSep); 124 | } 125 | void Load(const pugi::xml_node& xmlNode) 126 | { 127 | type = (LinkType)LoadInt(xmlNode, "type"); 128 | color = LoadImColor(xmlNode, "color"); 129 | thickness = LoadFloat(xmlNode, "thickness"); 130 | xSepIn = LoadFloat(xmlNode, "xSepIn"); 131 | xSepOut = LoadFloat(xmlNode, "xSepOut"); 132 | ykSep = LoadFloat(xmlNode, "ykSep"); 133 | } 134 | }; 135 | std::vector linkVec; 136 | void EraseLink(const CoreNodeInput* input); 137 | bool IsLinkVisible(ImVec2 pInput, ImVec2 pOutput) const; 138 | void DrawLinks() const; 139 | ImVec2 inputFreeLink; 140 | ImVec2 outputFreeLink; 141 | void SetLinkProperties(); 142 | void DrawFreeLink() const; 143 | void DrawLinkBezier(const Link& link, ImVec2 pInput, ImVec2 pOutput, float rounding, bool invert = false) const; 144 | void DrawLinkIOInv(const Link& link, ImVec2 pInput, ImVec2 pOutput, float dHandle) const; 145 | void DrawLinkBNInv(const Link& link, ImVec2 pInput, ImVec2 pOutput, float dHandle, bool invert = false) const; 146 | void SetInputSepUp(Link& link) const; 147 | void SetInputSepDown(Link& link) const; 148 | void SetOutputSepUp(Link& link) const; 149 | void SetOutputSepDown(Link& link) const; 150 | void SetNodeSep(Link& link) const; 151 | enum class LinkType 152 | { 153 | NONE, 154 | // BINV: both nodes inverted. 155 | BINV_LEFT, 156 | BINV_RIGHT_OVER, 157 | BINV_RIGHT_UNDER, 158 | BINV_RIGHT_MID, 159 | // IINV: only input node inverted. 160 | IINV_RIGHT_OVER, 161 | IINV_LEFT_OVER, 162 | IINV_RIGHT_UNDER, 163 | IINV_LEFT_UNDER, 164 | IINV_MID, 165 | // OINV: only output node inverted. 166 | OINV_RIGHT_OVER, 167 | OINV_LEFT_OVER, 168 | OINV_RIGHT_UNDER, 169 | OINV_LEFT_UNDER, 170 | OINV_MID, 171 | // NINV: No Inversion. Location of input node wrt output node. 172 | NINV_RIGHT, 173 | NINV_LEFT_OVER, 174 | NINV_LEFT_UNDER, 175 | NINV_LEFT_MID 176 | }; 177 | 178 | public: 179 | CoreDiagram() = default; 180 | virtual ~CoreDiagram(); 181 | void Update(); 182 | void Save(pugi::xml_node & xmlNode) const; 183 | void Load(const pugi::xml_node & xmlNode); 184 | bool GetModifFlag() const { return modifFlag; } 185 | void ResetModifFlag() { modifFlag = false; } 186 | void DrawLibrary() { coreLib.Draw(); } 187 | void DrawExplorer(); 188 | void DrawProperties(); 189 | }; 190 | 191 | #endif /* COREDIAGRAM_HPP */ -------------------------------------------------------------------------------- /core-nodes/CoreLibrary.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Library * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "CoreLibrary.hpp" 11 | 12 | CoreNode* CoreLibrary::GetNode(std::string_view libName, const std::string& uniqueName) 13 | { 14 | if (libName == "Gain") 15 | { 16 | return new GainNode(uniqueName); 17 | } 18 | if (libName == "Test") 19 | { 20 | return new TestNode(uniqueName); 21 | } 22 | return nullptr; 23 | } 24 | 25 | void CoreLibrary::Draw() 26 | { 27 | DrawTooltip(); 28 | 29 | int id = 0; // Branch id. 30 | if (ImGui::TreeNode("Nested")) 31 | { 32 | if (ImGui::TreeNode("Other")) 33 | { 34 | DrawBranch("Test", id, libMath); 35 | ImGui::TreePop(); 36 | } 37 | ImGui::TreePop(); 38 | } 39 | 40 | DrawBranch("Math", id, libMath); 41 | } 42 | 43 | void CoreLibrary::DrawTooltip() const 44 | { 45 | if (leafClicked == true) 46 | { 47 | ImGui::BeginTooltip(); 48 | ImGui::Text(std::string(selectedBranch + " / " + selectedLeaf).c_str()); 49 | ImGui::EndTooltip(); 50 | } 51 | } 52 | 53 | void CoreLibrary::DrawBranch(const std::string& name, int& id, const std::vector& vec) 54 | { 55 | id++; 56 | if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth)) 57 | { 58 | for (int i = 0; i < vec.size(); i++) 59 | { 60 | ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet; 61 | if (iSelectedLeaf == i && iSelectedBranch == id) 62 | { 63 | flags |= ImGuiTreeNodeFlags_Selected; 64 | } 65 | ImGui::TreeNodeEx((void*)(intptr_t)i, flags, vec.at(i).c_str()); 66 | if (ImGui::IsItemClicked()) 67 | { 68 | iSelectedLeaf = i; 69 | iSelectedBranch = id; 70 | selectedLeaf = vec.at(i); 71 | selectedBranch = name; 72 | leafClicked = true; 73 | } 74 | } 75 | ImGui::TreePop(); 76 | } 77 | } -------------------------------------------------------------------------------- /core-nodes/CoreLibrary.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Library * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef CORELIBRARY_HPP 11 | #define CORELIBRARY_HPP 12 | 13 | #include "CoreNode.hpp" 14 | #include "GainNode.hpp" 15 | #include "TestNode.hpp" 16 | 17 | class CoreLibrary 18 | { 19 | private: 20 | std::vector libMath = { "Gain", "Abs", "Product", "Test"}; 21 | 22 | int iSelectedLeaf = -1; 23 | int iSelectedBranch = -1; 24 | std::string selectedLeaf; 25 | std::string selectedBranch; 26 | bool leafClicked = false; 27 | void DrawBranch(const std::string& name, int& id, const std::vector& vec); 28 | void DrawTooltip() const; 29 | public: 30 | CoreLibrary() = default; 31 | virtual ~CoreLibrary() = default; 32 | CoreNode* GetNode(std::string_view libName, const std::string & uniqueName); 33 | 34 | void Draw(); 35 | bool IsLeafClicked() const { return leafClicked; } 36 | void SetLeafClickedFalse() { leafClicked = false; } 37 | std::string GetSelectedLeaf() const { return selectedLeaf; } 38 | }; 39 | 40 | #endif /* CORELIBRARY_HPP */ -------------------------------------------------------------------------------- /core-nodes/CoreNode.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Node * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "CoreNode.hpp" 11 | 12 | void CoreNode::AddInput(CoreNodeInput& input) 13 | { 14 | inputsWidth = ImMax(inputsWidth, input.GetRectPort().GetWidth()); 15 | inputsHeight += input.GetRectPort().GetHeight(); 16 | input.SetOrder(static_cast(inputVec.size())); 17 | inputVec.push_back(input); 18 | } 19 | 20 | void CoreNode::AddOutput(CoreNodeOutput& output) 21 | { 22 | outputsWidth = ImMax(outputsWidth, output.GetRectPort().GetWidth()); 23 | outputsHeight += output.GetRectPort().GetHeight(); 24 | output.SetOrder(static_cast(outputVec.size())); 25 | outputVec.push_back(output); 26 | } 27 | 28 | CoreNode::CoreNode(const std::string& name, const std::string& libName, NodeType type, ImColor colorNode) : name(name), libName(libName), type(type), colorNode(colorNode), nameEdited(name) 29 | { 30 | flagSet.SetFlag(NodeFlag::Default); 31 | 32 | colorHead.Value.x = colorNode.Value.x * 0.5f; 33 | colorHead.Value.y = colorNode.Value.y * 0.5f; 34 | colorHead.Value.z = colorNode.Value.z * 0.5f; 35 | colorHead.Value.w = 1.0f; 36 | colorLine = ImColor(0.416f, 0.016f, 0.059f, 0.75f); 37 | colorBody = ImColor(0.0f, 0.0f, 0.0f, 0.75f); 38 | } 39 | 40 | void CoreNode::Save(pugi::xml_node& xmlNode) 41 | { 42 | auto node = xmlNode.append_child("node"); 43 | node.append_attribute("name") = name.c_str(); 44 | SaveString(node, "libName", libName); 45 | SaveInt(node, "type", (int)type); 46 | SaveImColor(node, "colorNode", colorNode); 47 | SaveImColor(node, "colorHead", colorHead); 48 | SaveImColor(node, "colorLine", colorLine); 49 | SaveImColor(node, "colorBody", colorBody); 50 | SaveInt(node, "flagSet", flagSet.GetInt()); 51 | SaveImRect(node, "rectNode", rectNode); 52 | SaveImRect(node, "rectNodeTitle", rectNodeTitle); 53 | SaveImRect(node, "rectName", rectName); 54 | SaveFloat(node, "titleHeight", titleHeight); 55 | SaveFloat(node, "bodyHeight", bodyHeight); 56 | auto inputList = node.append_child("inputList"); 57 | for (const auto& element : inputVec) 58 | { 59 | element.Save(inputList); 60 | } 61 | auto outputList = node.append_child("outputList"); 62 | for (const auto& element : outputVec) 63 | { 64 | element.Save(outputList); 65 | } 66 | SaveImVec2(node, "leftPortPos", leftPortPos); 67 | SaveImVec2(node, "rightPortPos", rightPortPos); 68 | SaveBool(node, "portInverted", portInverted); 69 | SaveFloat(node, "inputsWidth", inputsWidth); 70 | SaveFloat(node, "inputsHeight", inputsHeight); 71 | SaveFloat(node, "outputsWidth", outputsWidth); 72 | SaveFloat(node, "outputsHeight", outputsHeight); 73 | 74 | SaveProperties(node.append_child("properties")); 75 | } 76 | 77 | void CoreNode::Load(const pugi::xml_node& xmlNode) 78 | { 79 | name = xmlNode.attribute("name").as_string(); 80 | libName = LoadString(xmlNode, "libName"); 81 | type = (NodeType)LoadInt(xmlNode, "type"); 82 | colorNode = LoadImColor(xmlNode, "colorNode"); 83 | colorHead = LoadImColor(xmlNode, "colorHead"); 84 | colorLine = LoadImColor(xmlNode, "colorLine"); 85 | colorBody = LoadImColor(xmlNode, "colorBody"); 86 | flagSet.SetInt(LoadInt(xmlNode, "flagSet")); 87 | flagSet.UnsetFlag(NodeFlag::Hovered); // Unset hovered flag. 88 | rectNode = LoadImRect(xmlNode, "rectNode"); 89 | rectNodeTitle = LoadImRect(xmlNode, "rectNodeTitle"); 90 | rectName = LoadImRect(xmlNode, "rectName"); 91 | titleHeight = LoadFloat(xmlNode, "titleHeight"); 92 | bodyHeight = LoadFloat(xmlNode, "bodyHeight"); 93 | for (const auto& element : xmlNode.child("inputList").children("input")) 94 | { 95 | inputVec.emplace_back(); 96 | inputVec.back().Load(element); 97 | } 98 | for (const auto& element : xmlNode.child("outputList").children("output")) 99 | { 100 | outputVec.emplace_back(); 101 | outputVec.back().Load(element); 102 | } 103 | leftPortPos = LoadImVec2(xmlNode, "leftPortPos"); 104 | rightPortPos = LoadImVec2(xmlNode, "rightPortPos"); 105 | portInverted = LoadBool(xmlNode, "portInverted"); 106 | inputsWidth = LoadFloat(xmlNode, "inputsWidth"); 107 | inputsHeight = LoadFloat(xmlNode, "inputsHeight"); 108 | outputsWidth = LoadFloat(xmlNode, "outputsWidth"); 109 | outputsHeight = LoadFloat(xmlNode, "outputsHeight"); 110 | nameEdited = name; 111 | 112 | LoadProperties(xmlNode.child("properties")); 113 | } 114 | 115 | bool CoreNode::IsNameUnique(std::string_view str, const std::vector& coreNodeVec) 116 | { 117 | for (auto n : coreNodeVec) 118 | { 119 | if (n->GetName() == str) 120 | { 121 | return false; 122 | } 123 | } 124 | return true; 125 | } 126 | 127 | void CoreNode::EditName(const std::vector& coreNodeVec) 128 | { 129 | ImGui::AlignTextToFramePadding(); 130 | ImGui::Text("name"); 131 | ImGui::SameLine(100.0f); 132 | ImGui::SetNextItemWidth(140.0f); 133 | ImGui::PushStyleColor(ImGuiCol_Text, editingName ? ImVec4(0.992f, 0.914f, 0.169f, 1.0f) : ImGuiStyle().Colors[ImGuiCol_Text]); 134 | if (ImGui::InputText(std::string("##" + name).c_str(), &nameEdited, ImGuiInputTextFlags_EnterReturnsTrue)) // ImGuiInputTextFlags_CharsNoBlank 135 | { 136 | if (IsNameUnique(nameEdited, coreNodeVec) == true) 137 | { 138 | name = nameEdited; 139 | auto loc = rectNode.Min; 140 | BuildGeometry(); 141 | if (portInverted == true) 142 | { 143 | auto delta = rightPortPos.x - leftPortPos.x; 144 | for (auto& input : inputVec) 145 | { 146 | input.Translate(ImVec2(delta, 0.0f)); 147 | } 148 | for (auto& output : outputVec) 149 | { 150 | output.Translate(ImVec2(-delta, 0.0f)); 151 | } 152 | } 153 | Translate(loc); 154 | modifFlag = true; 155 | } 156 | else if (nameEdited != name) 157 | { 158 | Notifier::Add(Notif(Notif::Type::ERROR, "The name \"" + nameEdited + "\"" + " already exists!", "Enter a unique name.", 5.0f)); 159 | nameEdited = name; 160 | } 161 | } 162 | editingName = ImGui::IsItemActive() ? true : false; 163 | ImGui::PopStyleColor(1); 164 | } 165 | 166 | void CoreNode::Translate(ImVec2 delta, bool selectedOnly) 167 | { 168 | if (selectedOnly && (flagSet.HasAnyFlag(NodeFlag::Selected) == false)) 169 | { 170 | return; // Do not translate unselected node in the node vector. 171 | } 172 | rectNode.Translate(delta); 173 | rectNodeTitle.Translate(delta); 174 | rectName.Translate(delta); 175 | for (auto& input : inputVec) 176 | { 177 | input.Translate(delta); 178 | } 179 | for (auto& output : outputVec) 180 | { 181 | output.Translate(delta); 182 | } 183 | } 184 | 185 | void CoreNode::InvertPort() 186 | { 187 | float delta; 188 | if (portInverted == false) 189 | { 190 | delta = rightPortPos.x - leftPortPos.x; 191 | portInverted = true; 192 | } 193 | else 194 | { 195 | delta = leftPortPos.x - rightPortPos.x; 196 | portInverted = false; 197 | } 198 | 199 | for (auto& input : inputVec) 200 | { 201 | input.Invert(); 202 | input.Translate(ImVec2(delta, 0.0f)); 203 | } 204 | for (auto& output : outputVec) 205 | { 206 | output.Invert(); 207 | output.Translate(ImVec2(-delta, 0.0f)); 208 | } 209 | } 210 | 211 | void CoreNode::BuildGeometry() 212 | { 213 | rectName.Min = ImVec2(0.0f, 0.0f); 214 | rectName.Max = ImGui::CalcTextSize(name.c_str()); 215 | titleHeight = kTitleHeight * rectName.GetHeight(); 216 | 217 | bodyHeight = ImMax(inputsHeight, outputsHeight) + kVerticalTop * rectName.GetHeight() + kVerticalBottom * rectName.GetHeight(); 218 | rectNode.Min = ImVec2(0.0f, 0.0f); 219 | rectNode.Max.x = ImMax(inputsWidth + outputsWidth + kHorizontal * rectName.GetHeight(), rectName.GetWidth() + kHorizontal * rectName.GetHeight()); 220 | rectNode.Max.y = titleHeight + bodyHeight; 221 | rectName.Translate(ImVec2((rectNode.GetWidth() - rectName.GetWidth()) * 0.5f, ((titleHeight - rectName.GetHeight()) * 0.5f))); 222 | 223 | leftPortPos = ImVec2(rectNode.GetTL().x, rectNode.GetTL().y + titleHeight + kVerticalTop * rectName.GetHeight()); 224 | rightPortPos = ImVec2(rectNode.GetTR().x, rectNode.GetTR().y + titleHeight + kVerticalTop * rectName.GetHeight()); 225 | for (auto& input : inputVec) 226 | { 227 | auto pinTopCenter = ImVec2(input.GetRectPin().GetCenter().x, input.GetRectPin().GetCenter().y - input.GetRectPin().GetHeight() * 0.5f); 228 | input.Translate(leftPortPos - pinTopCenter); 229 | leftPortPos.y += input.GetRectPort().GetHeight(); 230 | } 231 | for (auto& output : outputVec) 232 | { 233 | auto pinTopCenter = ImVec2(output.GetRectPin().GetCenter().x, output.GetRectPin().GetCenter().y - output.GetRectPin().GetHeight() * 0.5f); 234 | output.Translate(rightPortPos - pinTopCenter); 235 | rightPortPos.y += output.GetRectPort().GetHeight(); 236 | } 237 | 238 | rectNodeTitle.Min = ImVec2(0.0f, 0.0f); 239 | rectNodeTitle.Max.x = rectNode.Max.x; 240 | rectNodeTitle.Max.y = titleHeight; 241 | } 242 | 243 | void CoreNode::Draw(ImDrawList* drawList, ImVec2 offset, float scale) const 244 | { 245 | if (flagSet.HasAnyFlag(NodeFlag::Visible) == false) 246 | { 247 | return; 248 | } 249 | 250 | ImRect rect = rectNode; 251 | rect.Min *= scale; 252 | rect.Max *= scale; 253 | rect.Translate(offset); 254 | const float rounding = titleHeight * scale * 0.3f; 255 | const ImDrawFlags roundCornersFlags = ImDrawFlags_RoundCornersAll; 256 | 257 | if (flagSet.HasAnyFlag(NodeFlag::Hovered)) { /* TODO */ } 258 | if (flagSet.HasAnyFlag(NodeFlag::Disabled)) { /* TODO */ } 259 | 260 | // Body 261 | drawList->AddRectFilled(rect.Min, rect.Max, colorBody, rounding, roundCornersFlags); 262 | 263 | // Head 264 | const ImVec2 headBottomRight = rect.GetTR() + ImVec2(0.0f, titleHeight * scale); 265 | const ImDrawFlags headRoundCornersFlags = flagSet.HasAnyFlag(NodeFlag::Collapsed) ? roundCornersFlags : (ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight); 266 | drawList->AddRectFilled(rect.Min, headBottomRight, colorHead, rounding, headRoundCornersFlags); 267 | 268 | // Line 269 | if (flagSet.HasAnyFlag(NodeFlag::Collapsed) == false) 270 | { 271 | drawList->AddLine(ImVec2(rect.Min.x, headBottomRight.y), ImVec2(headBottomRight.x - 1.0f, headBottomRight.y), colorLine, 2.0f); 272 | } 273 | 274 | // Name and Shadow 275 | ImGui::SetCursorScreenPos(((rectName.Min + ImVec2(2, 2)) * scale) + offset); 276 | ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); 277 | ImGui::Text(name.c_str()); 278 | ImGui::PopStyleColor(); 279 | ImGui::SetCursorScreenPos((rectName.Min * scale) + offset); 280 | ImGui::Text(name.c_str()); 281 | 282 | // Selection 283 | if (flagSet.HasAnyFlag(NodeFlag::Selected)) 284 | { 285 | drawList->AddRectFilled(rect.Min, rect.Max, ImColor(1.0f, 1.0f, 1.0f, 0.25f), rounding, roundCornersFlags); 286 | } 287 | 288 | // Outline 289 | ImColor outlineColor = colorNode; 290 | float outlineThickness = 2.0f * scale; 291 | if (flagSet.HasAnyFlag(NodeFlag::Highlighted)) 292 | { 293 | outlineColor.Value.x *= 1.5f; 294 | outlineColor.Value.y *= 1.5f; 295 | outlineColor.Value.z *= 1.5f; 296 | outlineColor.Value.w = 1.0f; 297 | outlineThickness *= 1.2f; 298 | } 299 | else 300 | { 301 | outlineColor.Value.x *= 0.8f; 302 | outlineColor.Value.y *= 0.8f; 303 | outlineColor.Value.z *= 0.8f; 304 | outlineColor.Value.w = 1.0f; 305 | } 306 | drawList->AddRect(rect.Min, rect.Max, outlineColor, rounding, roundCornersFlags, outlineThickness); 307 | 308 | // Ports 309 | if (flagSet.HasAnyFlag(NodeFlag::Collapsed) == false) 310 | { 311 | for (const auto& input : inputVec) 312 | input.Draw(drawList, offset, scale); 313 | 314 | for (const auto& output : outputVec) 315 | output.Draw(drawList, offset, scale); 316 | } 317 | else 318 | { 319 | // No pin when collapsed. 320 | } 321 | } -------------------------------------------------------------------------------- /core-nodes/CoreNode.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Node * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef CORENODE_HPP 11 | #define CORENODE_HPP 12 | 13 | #include "CoreNodePort.hpp" 14 | 15 | struct NodeFlag 16 | { 17 | NodeFlag() = delete; 18 | static const unsigned int Default = 0; 19 | static const unsigned int Visible = 1 << 0; 20 | static const unsigned int Hovered = 1 << 1; 21 | static const unsigned int Selected = 1 << 2; 22 | static const unsigned int Collapsed = 1 << 3; 23 | static const unsigned int Disabled = 1 << 4; 24 | static const unsigned int Highlighted = 1 << 5; 25 | }; 26 | 27 | enum class NodeType 28 | { 29 | None = 0, 30 | Generic, // TODO change to core module? 31 | CoreIn, // maybe 32 | CoreOut // maybe 33 | }; 34 | 35 | class CoreNode 36 | { 37 | private: 38 | std::string name; 39 | std::string libName; 40 | NodeType type; 41 | ImColor colorNode; 42 | ImColor colorHead; 43 | ImColor colorLine; 44 | ImColor colorBody; 45 | FlagSet flagSet; 46 | ImRect rectNode; 47 | ImRect rectNodeTitle; 48 | ImRect rectName; 49 | float titleHeight{ 0.0f }; 50 | float bodyHeight{ 0.0f }; 51 | const float kTitleHeight{ 1.8f }; 52 | const float kHorizontal{ 1.7f }; 53 | const float kVerticalTop{ 0.50f }; 54 | const float kVerticalBottom{ 0.50f }; 55 | std::vector inputVec; 56 | std::vector outputVec; 57 | ImVec2 leftPortPos; 58 | ImVec2 rightPortPos; 59 | bool portInverted = false; 60 | float inputsWidth = 0.0f; 61 | float inputsHeight = 0.0f; 62 | float outputsWidth = 0.0f; 63 | float outputsHeight = 0.0f; 64 | std::string nameEdited; 65 | 66 | protected: 67 | void AddInput(CoreNodeInput& input); 68 | void AddOutput(CoreNodeOutput& output); 69 | void BuildGeometry(); 70 | virtual void SaveProperties(pugi::xml_node& xmlNode) = 0; 71 | virtual void LoadProperties(pugi::xml_node& xmlNode) = 0; 72 | bool modifFlag = false; 73 | 74 | static bool IsNameUnique(std::string_view str, const std::vector& coreNodeVec); 75 | void EditName(const std::vector& coreNodeVec); 76 | bool editingName = false; 77 | 78 | public: 79 | CoreNode() = default; 80 | CoreNode(const std::string& name, const std::string& libName, NodeType type, ImColor colorNode); 81 | virtual ~CoreNode() = default; 82 | void Save(pugi::xml_node& xmlNode); 83 | void Load(const pugi::xml_node& xmlNode); 84 | bool GetModifFlag() const { return modifFlag; } 85 | void ResetModifFlag() { modifFlag = false; } 86 | 87 | std::string GetName() const { return name; } 88 | std::string GetLibName() const { return libName; } 89 | NodeType GetType() const { return type; }; 90 | FlagSet& GetFlagSet() { return flagSet; } 91 | const FlagSet& GetFlagSet() const { return flagSet; } 92 | ImRect GetRectNode() const { return rectNode; } 93 | ImRect GetRectNodeTitle() const { return rectNodeTitle; } 94 | void SetRectNode(const ImRect& rect) { rectNode = rect; } 95 | float GetBodyHeight() const { return bodyHeight; } 96 | std::vector& GetInputVec() { return inputVec; } 97 | const std::vector& GetInputVec() const { return inputVec; } 98 | std::vector& GetOutputVec() { return outputVec; } 99 | 100 | void Translate(ImVec2 delta, bool selectedOnly = false); 101 | void Draw(ImDrawList* drawList, ImVec2 offset, float scale) const; 102 | void InvertPort(); 103 | bool IsPortInverted() const { return portInverted; } 104 | 105 | virtual void Build() = 0; 106 | virtual void DrawProperties(const std::vector& coreNodeVec) = 0; 107 | }; 108 | 109 | class NodeParamDouble 110 | { 111 | private: 112 | std::string name; 113 | bool edit = false; 114 | double data; 115 | 116 | public: 117 | explicit NodeParamDouble(const std::string& name, double v) : name(name), data(v) {} 118 | virtual ~NodeParamDouble() = default; 119 | double Get() const { return data; } 120 | void Set(double v) { data = v; } 121 | void Draw(bool& modifFlag, double step = 0.0, double stepFast = 0.0) 122 | { 123 | ImGui::AlignTextToFramePadding(); 124 | ImGui::Text(name.c_str()); 125 | ImGui::SameLine(100.0f); 126 | ImGui::SetNextItemWidth(140.0f); 127 | ImGui::PushStyleColor(ImGuiCol_Text, edit ? ImVec4(0.992f, 0.914f, 0.169f, 1.0f) : ImGuiStyle().Colors[ImGuiCol_Text]); 128 | if (ImGui::InputDouble(std::string("##" + name).c_str(), &data, step, stepFast, "%.15g", ImGuiInputTextFlags_EnterReturnsTrue)) 129 | { 130 | edit = false; 131 | modifFlag = true; 132 | } 133 | edit = ImGui::IsItemActive() ? true : false; 134 | ImGui::PopStyleColor(1); 135 | } 136 | }; 137 | 138 | #endif /* CORENODE_HPP */ -------------------------------------------------------------------------------- /core-nodes/CoreNodePort.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Node Port * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "CoreNodePort.hpp" 11 | 12 | CoreNodeInput::CoreNodeInput(const std::string& name, PortType type, PortDataType dataType) : 13 | name(name), type(type), dataType(dataType) 14 | { 15 | flagSet.SetFlag(PortFlag::Default); 16 | 17 | rectName.Min = ImVec2(0.0f, 0.0f); 18 | rectName.Max = ImGui::CalcTextSize(name.c_str()); 19 | ref = rectName.GetHeight(); 20 | rectPin = ImRect(ImVec2(0.0f, 0.0f), ImVec2((kPadding + kPin + kPadding) * ref, kHeight * ref)); 21 | rectName.Translate(ImVec2(rectPin.GetWidth(), (rectPin.GetHeight() - rectName.GetHeight()) * 0.5f)); 22 | rectPort.Min = ImVec2(0.0f, 0.0f); 23 | rectPort.Max = ImVec2(rectPin.GetWidth() + rectName.GetWidth() + kPadding * ref, rectPin.GetHeight()); 24 | ImVec2 offset = ImVec2(0.0f, 0.0f) - rectPin.GetCenter(); 25 | rectPin.Translate(offset); 26 | rectName.Translate(offset); 27 | rectPort.Translate(offset); 28 | } 29 | 30 | void CoreNodeInput::Save(pugi::xml_node& xmlNode) const 31 | { 32 | auto node = xmlNode.append_child("input"); 33 | node.append_attribute("name").set_value(name.c_str()); 34 | SaveImVec2(node, "position", position); 35 | SaveFloat(node, "ref", ref); 36 | SaveImRect(node, "rectName", rectName); 37 | SaveImRect(node, "rectPin", rectPin); 38 | SaveImRect(node, "rectPort", rectPort); 39 | SaveInt(node, "type", (int)type); 40 | SaveInt(node, "dataType", (int)dataType); 41 | SaveInt(node, "order", order); 42 | SaveInt(node, "flagSet", flagSet.GetInt()); 43 | SaveBool(node, "inverted", inverted); 44 | SaveInt(node, "linkDir", linkDir); 45 | SaveInt(node, "linkSepX", linkSepX); 46 | SaveInt(node, "linkSepY", linkSepY); 47 | SaveInt(node, "targetLinkDir", targetLinkDir); 48 | SaveInt(node, "targetLinkSep", targetLinkSep); 49 | } 50 | 51 | void CoreNodeInput::Load(const pugi::xml_node & xmlNode) 52 | { 53 | name = xmlNode.attribute("name").as_string(); 54 | position = LoadImVec2(xmlNode, "position"); 55 | ref = LoadFloat(xmlNode, "ref"); 56 | rectName = LoadImRect(xmlNode, "rectName"); 57 | rectPin = LoadImRect(xmlNode, "rectPin"); 58 | rectPort = LoadImRect(xmlNode, "rectPort"); 59 | type = (PortType)LoadInt(xmlNode, "type"); 60 | dataType = (PortDataType)LoadInt(xmlNode, "dataType"); 61 | order = LoadInt(xmlNode, "order"); 62 | flagSet.SetInt(LoadInt(xmlNode, "flagSet")); 63 | inverted = LoadBool(xmlNode, "inverted"); 64 | linkDir = LoadInt(xmlNode, "linkDir"); 65 | linkSepX = LoadInt(xmlNode, "linkSepX"); 66 | linkSepY = LoadInt(xmlNode, "linkSepY"); 67 | targetLinkDir = LoadInt(xmlNode, "targetLinkDir"); 68 | targetLinkSep = LoadInt(xmlNode, "targetLinkSep"); 69 | } 70 | 71 | void CoreNodeInput::BreakLink() 72 | { 73 | if (targetNodeOutput != nullptr) 74 | { 75 | targetNodeOutput->DecreaseLinkNum(); 76 | } 77 | targetNode = nullptr; 78 | targetNodeOutput = nullptr; 79 | linkDir = 0; 80 | linkSepX = 0; 81 | linkSepY = 0; 82 | targetLinkDir = 0; 83 | targetLinkSep = 0; 84 | } 85 | 86 | void CoreNodeInput::Translate(ImVec2 delta) 87 | { 88 | position += delta; 89 | rectPort.Translate(delta); 90 | rectName.Translate(delta); 91 | rectPin.Translate(delta); 92 | } 93 | 94 | void CoreNodeInput::Invert() 95 | { 96 | float deltaPort = rectName.GetWidth() + kPadding * ref; 97 | float deltaName = rectName.GetWidth() + rectPin.GetWidth(); 98 | if (inverted == false) 99 | { 100 | rectPort.Translate(ImVec2(-deltaPort, 0.0f)); 101 | rectName.Translate(ImVec2(-deltaName, 0.0f)); 102 | inverted = true; 103 | } 104 | else 105 | { 106 | rectPort.Translate(ImVec2(deltaPort, 0.0f)); 107 | rectName.Translate(ImVec2(deltaName, 0.0f)); 108 | inverted = false; 109 | } 110 | } 111 | 112 | void CoreNodeInput::Draw(ImDrawList* drawList, ImVec2 offset, float scale) const 113 | { 114 | if (type == PortType::None) 115 | { 116 | return; 117 | } 118 | 119 | ImGui::SetCursorScreenPos((rectName.Min * scale) + offset); 120 | ImGui::Text(name.c_str()); 121 | 122 | // Show type. 123 | if (flagSet.Equal(PortFlag::Draging) && targetNode == nullptr) 124 | { 125 | std::string typeName = portTypeNames.at(static_cast(dataType)); 126 | ImVec2 typeNameSize = ImGui::CalcTextSize(typeName.c_str()); 127 | auto distText = ImVec2(10.0f, 0.0f); 128 | if (inverted == true) 129 | { 130 | auto point = ImVec2(rectName.Min.x + rectName.GetWidth() + rectPin.GetWidth(), rectName.Min.y) + distText; 131 | ImGui::SetCursorScreenPos((point * scale) + offset); 132 | } 133 | else 134 | { 135 | auto point = ImVec2(rectName.Min.x - rectPin.GetWidth(), rectName.Min.y) - distText; 136 | ImGui::SetCursorScreenPos((point * scale - ImVec2(typeNameSize.x, 0.0f)) + offset); 137 | } 138 | auto typeColor = ImColor(0.996f, 0.431f, 0.000f, 1.0f); // data type text color 139 | ImGui::TextColored(typeColor, typeName.c_str()); 140 | } 141 | 142 | auto center = ImVec2((position * scale) + offset); 143 | auto radius1 = kPin * rectName.GetHeight() * 0.5f * scale; 144 | auto radius2 = kPin * rectName.GetHeight() * 0.5f * scale * 0.5f; 145 | auto radius3 = kPin * rectName.GetHeight() * 0.5f * scale * 0.8f; 146 | auto thickness1 = 0.1f * rectName.GetHeight() * scale; 147 | 148 | auto color1 = ImColor(0.000f, 0.459f, 0.388f, 1.0f); // background 149 | auto color2 = ImColor(0.341f, 0.659f, 0.769f, 1.0f); // teal ring 150 | auto color3 = ImColor(1.000f, 1.000f, 1.000f, 1.0f); // white 151 | auto color4 = ImColor(0.996f, 0.431f, 0.000f, 1.0f); // orange 152 | auto color5 = ImColor(1.000f, 0.824f, 0.000f, 1.0f); // orange for ic 153 | 154 | drawList->AddCircleFilled(center, radius1, color1, 0); // Background 155 | drawList->AddCircle(center, radius1, color2, 0, thickness1); // Outer ring 156 | 157 | float distTri = radius1 * 1.6f; 158 | float triLength = radius1; 159 | if (flagSet.Equal(PortFlag::Draging) && type == PortType::In && targetNode == nullptr) 160 | { 161 | if (inverted == false) 162 | { 163 | auto loc = ImVec2(center.x - distTri, center.y); 164 | drawList->AddTriangleFilled(loc + ImVec2(-triLength, triLength), loc + ImVec2(-triLength, -triLength), loc, color4); 165 | } 166 | else 167 | { 168 | auto loc = ImVec2(center.x + distTri, center.y); 169 | drawList->AddTriangleFilled(loc + ImVec2(triLength, triLength), loc + ImVec2(triLength, -triLength), loc, color4); 170 | } 171 | } 172 | else if (flagSet.Equal(PortFlag::Draging) && type == PortType::Ic && targetNode == nullptr) 173 | { 174 | if (inverted == false) 175 | { 176 | auto loc = ImVec2(center.x - distTri, center.y); 177 | drawList->AddTriangleFilled(loc + ImVec2(-triLength, triLength), loc + ImVec2(-triLength, -triLength), loc, color5); 178 | } 179 | else 180 | { 181 | auto loc = ImVec2(center.x + distTri, center.y); 182 | drawList->AddTriangleFilled(loc + ImVec2(triLength, triLength), loc + ImVec2(triLength, -triLength), loc, color5); 183 | } 184 | } 185 | 186 | if (targetNode == nullptr) 187 | { 188 | if (flagSet.Equal(PortFlag::Hovered)) 189 | { 190 | drawList->AddCircleFilled(center, radius2, color4); 191 | } 192 | if (flagSet.Equal(PortFlag::Connectible)) 193 | { 194 | drawList->AddCircleFilled(center, radius2, color4); 195 | } 196 | if (flagSet.Equal(PortFlag::Draging)) 197 | { 198 | drawList->AddCircleFilled(center, radius3, color4); 199 | } 200 | if (flagSet.Equal(PortFlag::Hovered | PortFlag::Connectible)) 201 | { 202 | drawList->AddCircleFilled(center, radius3, color4); 203 | } 204 | } 205 | else 206 | { 207 | drawList->AddCircleFilled(center, radius2, color3); // Inner circle 208 | if (flagSet.Equal(PortFlag::Hovered)) 209 | { 210 | // drawList->AddRect(rectPort.Min * scale + offset, rectPort.Max * scale + offset, ImColor(1.0f, 0.0f, 0.0f, 0.5f)); 211 | } 212 | if (flagSet.Equal(PortFlag::Connectible)) 213 | { 214 | drawList->AddCircleFilled(center, radius2, color4); 215 | } 216 | } 217 | } 218 | 219 | CoreNodeOutput::CoreNodeOutput(const std::string& name, PortType type, PortDataType dataType) : 220 | name(name), type(type), dataType(dataType) 221 | { 222 | flagSet.SetFlag(PortFlag::Default); 223 | 224 | rectName.Min = ImVec2(0.0f, 0.0f) - ImGui::CalcTextSize(name.c_str()); 225 | rectName.Max = ImVec2(0.0f, 0.0f); 226 | ref = rectName.GetHeight(); 227 | rectPin = ImRect(ImVec2((kPadding + kPin + kPadding) * ref * -1.0f, kHeight * ref * -1.0f), ImVec2(0.0f, 0.0f)); 228 | rectName.Translate(ImVec2(-rectPin.GetWidth(), (rectPin.GetHeight() - rectName.GetHeight()) * 0.5f * -1.0f)); 229 | rectPort.Min = ImVec2(-rectPin.GetWidth() + -rectName.GetWidth() - kPadding * ref, -rectPin.GetHeight()); 230 | rectPort.Max = ImVec2(0.0f, 0.0f); 231 | ImVec2 offset = ImVec2(0.0f, 0.0f) - rectPin.GetCenter(); 232 | rectPin.Translate(offset); 233 | rectPort.Translate(offset); 234 | rectName.Translate(offset); 235 | } 236 | 237 | void CoreNodeOutput::Save(pugi::xml_node & xmlNode) const 238 | { 239 | auto node = xmlNode.append_child("output"); 240 | node.append_attribute("name").set_value(name.c_str()); 241 | SaveImVec2(node, "position", position); 242 | SaveFloat(node, "ref", ref); 243 | SaveImRect(node, "rectName", rectName); 244 | SaveImRect(node, "rectPin", rectPin); 245 | SaveImRect(node, "rectPort", rectPort); 246 | SaveInt(node, "type", (int)type); 247 | SaveInt(node, "dataType", (int)dataType); 248 | SaveInt(node, "order", order); 249 | SaveInt(node, "flagSet", flagSet.GetInt()); 250 | SaveInt(node, "linkNum", linkNum); 251 | SaveBool(node, "inverted", inverted); 252 | } 253 | 254 | void CoreNodeOutput::Load(const pugi::xml_node & xmlNode) 255 | { 256 | name = xmlNode.attribute("name").as_string(); 257 | position = LoadImVec2(xmlNode, "position"); 258 | ref = LoadFloat(xmlNode, "ref"); 259 | rectName = LoadImRect(xmlNode, "rectName"); 260 | rectPin = LoadImRect(xmlNode, "rectPin"); 261 | rectPort = LoadImRect(xmlNode, "rectPort"); 262 | type = (PortType)LoadInt(xmlNode, "type"); 263 | dataType = (PortDataType)LoadInt(xmlNode, "dataType"); 264 | order = LoadInt(xmlNode, "order"); 265 | flagSet.SetInt(LoadInt(xmlNode, "flagSet")); 266 | linkNum = LoadInt(xmlNode, "linkNum"); 267 | inverted = LoadBool(xmlNode, "inverted"); 268 | } 269 | 270 | void CoreNodeOutput::Translate(ImVec2 delta) 271 | { 272 | position += delta; 273 | rectPort.Translate(delta); 274 | rectName.Translate(delta); 275 | rectPin.Translate(delta); 276 | } 277 | 278 | void CoreNodeOutput::Invert() 279 | { 280 | float deltaPort = rectName.GetWidth() + kPadding * ref; 281 | float deltaName = rectName.GetWidth() + rectPin.GetWidth(); 282 | if (inverted == false) 283 | { 284 | rectPort.Translate(ImVec2(deltaPort, 0.0f)); 285 | rectName.Translate(ImVec2(deltaName, 0.0f)); 286 | inverted = true; 287 | } 288 | else 289 | { 290 | rectPort.Translate(ImVec2(-deltaPort, 0.0f)); 291 | rectName.Translate(ImVec2(-deltaName, 0.0f)); 292 | inverted = false; 293 | } 294 | } 295 | 296 | void CoreNodeOutput::Draw(ImDrawList* drawList, ImVec2 offset, float scale) const 297 | { 298 | if (type == PortType::None) 299 | { 300 | return; 301 | } 302 | 303 | ImGui::SetCursorScreenPos((rectName.Min * scale) + offset); 304 | ImGui::Text(name.c_str()); 305 | 306 | // Show type. 307 | if (flagSet.Equal(PortFlag::Draging)) 308 | { 309 | std::string typeName = portTypeNames.at(static_cast(dataType)); 310 | ImVec2 typeNameSize = ImGui::CalcTextSize(typeName.c_str()); 311 | auto distText = ImVec2(10.0f, 0.0f); 312 | if (inverted == false) 313 | { 314 | auto point = ImVec2(rectName.Min.x + rectName.GetWidth() + rectPin.GetWidth(), rectName.Min.y) + distText; 315 | ImGui::SetCursorScreenPos((point * scale) + offset); 316 | } 317 | else 318 | { 319 | auto point = ImVec2(rectName.Min.x - rectPin.GetWidth(), rectName.Min.y) - distText; 320 | ImGui::SetCursorScreenPos((point * scale - ImVec2(typeNameSize.x, 0.0f)) + offset); 321 | } 322 | auto typeColor = ImColor(0.996f, 0.431f, 0.000f, 1.0f); // data type text color 323 | ImGui::TextColored(typeColor, typeName.c_str()); 324 | } 325 | 326 | auto center = ImVec2((position * scale) + offset); 327 | auto radius1 = kPin * rectName.GetHeight() * 0.5f * scale; 328 | auto radius2 = kPin * rectName.GetHeight() * 0.5f * scale * 0.5f; 329 | auto radius3 = kPin * rectName.GetHeight() * 0.5f * scale * 0.8f; 330 | 331 | auto thickness1 = 0.1f * rectName.GetHeight() * scale; 332 | 333 | auto color1 = ImColor(0.000f, 0.459f, 0.388f, 1.0f); // background 334 | auto color2 = ImColor(0.341f, 0.659f, 0.769f, 1.0f); // teal ring 335 | auto color3 = ImColor(1.000f, 1.000f, 1.000f, 1.0f); // white 336 | auto color4 = ImColor(0.996f, 0.431f, 0.000f, 1.0f); // orange 337 | 338 | drawList->AddCircleFilled(center, radius1, color1, 0); // Background 339 | drawList->AddCircle(center, radius1, color2, 0, thickness1); // Outer ring 340 | 341 | float distTri = radius1 * 1.6f; 342 | float triLength = radius1; 343 | if (flagSet.Equal(PortFlag::Draging) && type == PortType::Out) 344 | { 345 | if (inverted == false) 346 | { 347 | auto loc = ImVec2(center.x + distTri + radius1, center.y); 348 | drawList->AddTriangleFilled(loc + ImVec2(-triLength, triLength), loc + ImVec2(-triLength, -triLength), loc, color4); 349 | } 350 | else 351 | { 352 | auto loc = ImVec2(center.x - distTri - radius1, center.y); 353 | drawList->AddTriangleFilled(loc + ImVec2(triLength, triLength), loc + ImVec2(triLength, -triLength), loc, color4); 354 | } 355 | } 356 | 357 | if (linkNum == 0) 358 | { 359 | if (flagSet.Equal(PortFlag::Hovered)) 360 | { 361 | drawList->AddCircleFilled(center, radius2, color4); 362 | } 363 | if (flagSet.Equal(PortFlag::Connectible)) 364 | { 365 | drawList->AddCircleFilled(center, radius2, color4); 366 | } 367 | if (flagSet.Equal(PortFlag::Draging)) 368 | { 369 | drawList->AddCircleFilled(center, radius3, color4); 370 | } 371 | if (flagSet.Equal(PortFlag::Hovered | PortFlag::Connectible)) 372 | { 373 | drawList->AddCircleFilled(center, radius3, color4); 374 | } 375 | } 376 | else 377 | { 378 | drawList->AddCircleFilled(center, radius2, color3); // Inner circle 379 | if (flagSet.Equal(PortFlag::Hovered)) 380 | { 381 | // drawList->AddRect(rectPort.Min * scale + offset, rectPort.Max * scale + offset, ImColor(1.0f, 0.0f, 0.0f, 0.5f)); 382 | drawList->AddCircleFilled(center, radius2, color4); 383 | } 384 | if (flagSet.Equal(PortFlag::Draging)) 385 | { 386 | drawList->AddCircleFilled(center, radius3, color4); 387 | } 388 | if (flagSet.Equal(PortFlag::Connectible)) 389 | { 390 | drawList->AddCircleFilled(center, radius2, color4); 391 | } 392 | } 393 | } -------------------------------------------------------------------------------- /core-nodes/CoreNodePort.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Core Node Port * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef CORENODEPORT_HPP 11 | #define CORENODEPORT_HPP 12 | 13 | #define IMGUI_DEFINE_MATH_OPERATORS 14 | #include 15 | #include "FileDialog.hpp" 16 | 17 | const std::vector portTypeNames 18 | { 19 | "Generic", 20 | "Int", 21 | "Float", 22 | "Double", 23 | "Vector", 24 | "Image", 25 | "Text" 26 | }; 27 | 28 | enum class PortDataType 29 | { 30 | Generic, 31 | Int, 32 | Float, 33 | Double, 34 | Vector, 35 | Image, 36 | Text 37 | // Warning! If you update this, update port type names vector. 38 | }; 39 | 40 | enum class PortType 41 | { 42 | None, // None. 43 | In, // Input 44 | Ic, // Initial condition 45 | Out // Output 46 | }; 47 | 48 | class FlagSet 49 | { 50 | private: 51 | unsigned int flags = 0; 52 | public: 53 | void SetFlag(unsigned int flag) { flags |= flag; } 54 | void UnsetFlag(unsigned int flag) { flags &= ~flag; } 55 | void FlipFlag(unsigned int flag) { flags ^= flag; } 56 | bool HasFlag(unsigned int flag) const { return (flags & flag) == flag; } 57 | bool HasAnyFlag(unsigned int multiFlag) const { return (flags & multiFlag) != 0; } 58 | bool Equal(unsigned int multiFlag) const { return flags == multiFlag; } 59 | std::string Get() const { return std::bitset<8 * sizeof(flags)>(flags).to_string(); } 60 | int GetInt() const { return (int)flags; } 61 | void SetInt(int argFlags) { flags = argFlags; } 62 | }; 63 | 64 | struct PortFlag 65 | { 66 | PortFlag() = delete; 67 | static const unsigned int Default = 0; 68 | //static const unsigned int Visible = 1 << 0; // not used. 69 | static const unsigned int Hovered = 1 << 1; 70 | static const unsigned int Connectible = 1 << 2; 71 | static const unsigned int Draging = 1 << 3; 72 | }; 73 | 74 | class CoreNode; 75 | class CoreNodeOutput; 76 | class CoreNodeInput 77 | { 78 | private: 79 | ImVec2 position; 80 | float ref; 81 | ImRect rectName; 82 | ImRect rectPin; 83 | ImRect rectPort; 84 | const float kPin{ 0.6f }; 85 | const float kPadding{ 0.25f }; 86 | const float kHeight{ 1.25f }; 87 | std::string name; 88 | PortType type; 89 | PortDataType dataType; 90 | int order; 91 | FlagSet flagSet; 92 | CoreNode* targetNode{ nullptr }; 93 | CoreNodeOutput* targetNodeOutput{ nullptr }; 94 | bool inverted = false; 95 | int linkDir = 0; // direction of the input on the y axis {-1, 0, 1}. +1:downwards. 96 | int linkSepX = 0; // seperation coeff. for the horizontal axis. 97 | int linkSepY = 0; // seperation coeff. for the vertical axis (between nodes). 98 | int targetLinkDir = 0; // direction of the linked output. 99 | int targetLinkSep = 0; // seperation coeff. of the linked output. 100 | public: 101 | CoreNodeInput() = default; 102 | CoreNodeInput(const std::string& name, PortType type, PortDataType dataType); 103 | virtual ~CoreNodeInput() = default; 104 | void Save(pugi::xml_node& xmlNode) const; 105 | void Load(const pugi::xml_node& xmlNode); 106 | 107 | std::string GetName() const { return name; } 108 | PortType GetType() const { return type; }; 109 | PortDataType GetDataType() const { return dataType; }; 110 | int GetOrder() const { return order; } 111 | void SetOrder(int i) { order = i; } 112 | ImVec2 GetPosition() const { return position; } 113 | ImRect GetRectPin() const { return rectPin; } 114 | ImRect GetRectPort() const { return rectPort; } 115 | FlagSet& GetFlagSet() { return flagSet; } 116 | CoreNode* GetTargetNode() const { return targetNode; } 117 | void SetTargetNode(CoreNode* node) { targetNode = node; } 118 | CoreNodeOutput* GetTargetNodeOutput() const { return targetNodeOutput; } 119 | void SetTargetNodeOutput(CoreNodeOutput* nodeOutput) { targetNodeOutput = nodeOutput; } 120 | void BreakLink(); 121 | void SetLinkDir(int dir) { linkDir = dir; } 122 | int GetLinkDir() const { return linkDir; } 123 | void SetLinkSepX(int sep) { linkSepX = sep; } 124 | int GetLinkSepX() const { return linkSepX; } 125 | void SetLinkSepY(int sep) { linkSepY = sep; } 126 | int GetLinkSepY() const { return linkSepY; } 127 | void SetTargetLinkDir(int dir) { targetLinkDir = dir; } 128 | int GetTargetLinkDir() const { return targetLinkDir; } 129 | void SetTargetLinkSep(int sep) { targetLinkSep = sep; } 130 | int GetTargetLinkSep() const { return targetLinkSep; } 131 | void Translate(ImVec2 delta); 132 | void Draw(ImDrawList* drawList, ImVec2 offset, float scale) const; 133 | void Invert(); 134 | }; 135 | 136 | class CoreNodeOutput 137 | { 138 | private: 139 | ImVec2 position; 140 | float ref; 141 | ImRect rectName; 142 | ImRect rectPin; 143 | ImRect rectPort; 144 | const float kPin{ 0.6f }; 145 | const float kPadding{ 0.25f }; 146 | const float kHeight{ 1.25f }; 147 | std::string name; 148 | PortType type; 149 | PortDataType dataType; 150 | int order; 151 | FlagSet flagSet; 152 | int linkNum{ 0 }; 153 | bool inverted = false; 154 | public: 155 | CoreNodeOutput() = default; 156 | CoreNodeOutput(const std::string& name, PortType type, PortDataType dataType); 157 | virtual ~CoreNodeOutput() = default; 158 | void Save(pugi::xml_node& xmlNode) const; 159 | void Load(const pugi::xml_node& xmlNode); 160 | 161 | std::string GetName() const { return name; } 162 | PortType GetType() const { return type; }; 163 | PortDataType GetDataType() const { return dataType; }; 164 | int GetOrder() const { return order; } 165 | void SetOrder(int i) { order = i; } 166 | ImVec2 GetPosition() const { return position; } 167 | ImRect GetRectPin() const { return rectPin; } 168 | ImRect GetRectPort() const { return rectPort; } 169 | FlagSet& GetFlagSet() { return flagSet; } 170 | int GetLinkNum() const { return linkNum; } 171 | void IncreaseLinkNum() { linkNum += 1; } 172 | void DecreaseLinkNum() { linkNum > 0 ? linkNum -= 1 : linkNum = 0; } 173 | void Translate(ImVec2 delta); 174 | void Draw(ImDrawList* drawList, ImVec2 offset, float scale) const; 175 | void Invert(); 176 | }; 177 | 178 | #endif /* CORENODEPORT_HPP */ -------------------------------------------------------------------------------- /core-nodes/FileDialog.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * File Dialog * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "FileDialog.hpp" 11 | 12 | bool FileDialog::Draw(bool* open) 13 | { 14 | if (*open == false) 15 | { 16 | return false; 17 | } 18 | else 19 | { 20 | ImGui::OpenPopup(title.c_str()); 21 | } 22 | 23 | bool done = false; 24 | title = (type == Type::OPEN) ? "Open File" : "Save File"; 25 | ImGui::SetNextWindowSize(ImVec2(660.0f, 410.0f), ImGuiCond_Once); 26 | ImGui::SetNextWindowSizeConstraints(ImVec2(410, 410), ImVec2(1080, 410)); 27 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 28 | if (ImGui::BeginPopupModal(title.c_str(), open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) 29 | { 30 | if (currentFiles.empty() && currentDirectories.empty() || refresh) 31 | { 32 | refresh = false; 33 | currentIndex = 0; 34 | currentFiles.clear(); 35 | currentDirectories.clear(); 36 | for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(directoryPath)) 37 | { 38 | entry.is_directory() ? currentDirectories.push_back(entry) : currentFiles.push_back(entry); 39 | } 40 | } 41 | 42 | // Path 43 | ImGui::Text("%s", directoryPath.string().c_str()); 44 | ImGui::BeginChild("##browser", ImVec2(ImGui::GetWindowContentRegionWidth(), 300.0f), true, ImGuiWindowFlags_None); 45 | size_t index = 0; 46 | 47 | // Parent 48 | if (directoryPath.has_parent_path()) 49 | { 50 | if (ImGui::Selectable("..", currentIndex == index, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(ImGui::GetWindowContentRegionWidth(), 0))) 51 | { 52 | currentIndex = index; 53 | if (ImGui::IsMouseDoubleClicked(0)) 54 | { 55 | directoryPath = directoryPath.parent_path(); 56 | refresh = true; 57 | } 58 | } 59 | index++; 60 | } 61 | 62 | // Directories 63 | for (const auto& element : currentDirectories) 64 | { 65 | ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 210, 0, 255)); 66 | if (ImGui::Selectable(element.path().filename().string().c_str(), currentIndex == index, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(ImGui::GetWindowContentRegionWidth(), 0))) 67 | { 68 | currentIndex = index; 69 | if (ImGui::IsMouseDoubleClicked(0)) 70 | { 71 | directoryPath = element.path(); 72 | refresh = true; 73 | } 74 | } 75 | ImGui::PopStyleColor(); 76 | index++; 77 | } 78 | 79 | // Files 80 | for (const auto& element : currentFiles) 81 | { 82 | if (ImGui::Selectable(element.path().filename().string().c_str(), currentIndex == index, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(ImGui::GetWindowContentRegionWidth(), 0))) 83 | { 84 | currentIndex = index; 85 | fileName = element.path().filename(); 86 | } 87 | index++; 88 | } 89 | ImGui::EndChild(); 90 | 91 | // Draw filename 92 | size_t fileNameSize = fileName.string().size(); 93 | if (fileNameSize >= bufferSize) 94 | { 95 | fileNameSize = bufferSize - 1; 96 | } 97 | std::memcpy(buffer, fileName.string().c_str(), fileNameSize); 98 | buffer[fileNameSize] = 0; 99 | 100 | ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth()); 101 | ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 200, 0, 255)); 102 | if (ImGui::InputText("File Name", buffer, bufferSize)) 103 | { 104 | fileName = std::string(buffer); 105 | currentIndex = 0; 106 | } 107 | ImGui::PopStyleColor(); 108 | if (ImGui::Button("Cancel")) 109 | { 110 | refresh = false; 111 | currentIndex = 0; 112 | currentFiles.clear(); 113 | currentDirectories.clear(); 114 | *open = false; 115 | } 116 | ImGui::SameLine(); 117 | resultPath = directoryPath / fileName; 118 | if (type == Type::OPEN) 119 | { 120 | if (ImGui::Button("Open")) 121 | { 122 | if (std::filesystem::exists(resultPath) && fileName.string().rfind(".") != std::string::npos 123 | && fileName.string().substr(fileName.string().rfind(".")) == fileFormat) 124 | { 125 | refresh = false; 126 | currentIndex = 0; 127 | currentFiles.clear(); 128 | currentDirectories.clear(); 129 | done = true; 130 | *open = false; 131 | } 132 | else 133 | { 134 | Notifier::Add(Notif(Notif::Type::ERROR, "", "File format is not valid.")); 135 | } 136 | } 137 | } 138 | else if (type == Type::SAVE) 139 | { 140 | const auto beforeFormatCheck = resultPath.string(); 141 | bool isFormatCorrect = false; 142 | if (auto dot = fileName.string().rfind("."); dot == std::string::npos) 143 | { 144 | resultPath = resultPath.string() + fileFormat; 145 | } 146 | else if(fileName.string().substr(dot) != fileFormat) 147 | { 148 | resultPath = resultPath.string() + fileFormat; 149 | } 150 | else 151 | { 152 | isFormatCorrect = true; 153 | } 154 | if (ImGui::Button("Save")) 155 | { 156 | if (std::filesystem::exists(beforeFormatCheck) == true && isFormatCorrect == false) 157 | { 158 | Notifier::Add(Notif(Notif::Type::ERROR, "", "Another file exists with the same name.")); 159 | } 160 | else if (std::filesystem::exists(resultPath) == false) 161 | { 162 | refresh = false; 163 | currentIndex = 0; 164 | currentFiles.clear(); 165 | currentDirectories.clear(); 166 | done = true; 167 | *open = false; 168 | } 169 | else 170 | { 171 | ImGui::OpenPopup("Override?"); 172 | } 173 | } 174 | ImGui::SetNextWindowPos(ImGui::GetCurrentWindow()->Rect().GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 175 | if (ImGui::BeginPopupModal("Override?", nullptr, ImGuiWindowFlags_NoResize)) 176 | { 177 | ImGui::Text("File already exists. Do you want to override?"); 178 | ImGui::Separator(); 179 | if (ImGui::Button("Yes", ImVec2(50.0f, 0.0f))) 180 | { 181 | refresh = false; 182 | currentIndex = 0; 183 | currentFiles.clear(); 184 | currentDirectories.clear(); 185 | done = true; 186 | *open = false; 187 | } 188 | ImGui::SameLine(); 189 | if (ImGui::Button("No", ImVec2(50.0f, 0.0f))) 190 | { 191 | ImGui::CloseCurrentPopup(); 192 | } 193 | ImGui::EndPopup(); 194 | } 195 | } 196 | ImGui::EndPopup(); 197 | } 198 | return done; 199 | } 200 | 201 | void SaveImRect(pugi::xml_node& xmlNode, const std::string& name, const ImRect& rect) 202 | { 203 | auto child = xmlNode.append_child(name.c_str()); 204 | child.append_attribute("xMin") = rect.Min.x; 205 | child.append_attribute("xMax") = rect.Max.x; 206 | child.append_attribute("yMin") = rect.Min.y; 207 | child.append_attribute("yMax") = rect.Max.y; 208 | } 209 | void SaveImVec2(pugi::xml_node& xmlNode, const std::string& name, const ImVec2& vec) 210 | { 211 | auto child = xmlNode.append_child(name.c_str()); 212 | child.append_attribute("x") = vec.x; 213 | child.append_attribute("y") = vec.y; 214 | } 215 | void SaveImColor(pugi::xml_node& xmlNode, const std::string& name, const ImColor& color) 216 | { 217 | auto child = xmlNode.append_child(name.c_str()); 218 | child.append_attribute("x") = color.Value.x; 219 | child.append_attribute("y") = color.Value.y; 220 | child.append_attribute("z") = color.Value.z; 221 | child.append_attribute("w") = color.Value.w; 222 | } 223 | void SaveFloat(pugi::xml_node& xmlNode, const std::string& name, float data) 224 | { 225 | xmlNode.append_child(name.c_str()).append_attribute("data").set_value(data); 226 | } 227 | void SaveDouble(pugi::xml_node& xmlNode, const std::string& name, double data) 228 | { 229 | xmlNode.append_child(name.c_str()).append_attribute("data").set_value(data); 230 | } 231 | void SaveString(pugi::xml_node& xmlNode, const std::string& name, const std::string& str) 232 | { 233 | xmlNode.append_child(name.c_str()).append_attribute("data").set_value(str.c_str()); 234 | } 235 | void SaveInt(pugi::xml_node& xmlNode, const std::string& name, int i) 236 | { 237 | xmlNode.append_child(name.c_str()).append_attribute("data").set_value(i); 238 | } 239 | void SaveBool(pugi::xml_node& xmlNode, const std::string& name, bool b) 240 | { 241 | xmlNode.append_child(name.c_str()).append_attribute("data").set_value(b); 242 | } 243 | 244 | ImRect LoadImRect(const pugi::xml_node & xmlNode, const std::string & name) 245 | { 246 | auto min = ImVec2(xmlNode.child(name.c_str()).attribute("xMin").as_float(), xmlNode.child(name.c_str()).attribute("yMin").as_float()); 247 | auto max = ImVec2(xmlNode.child(name.c_str()).attribute("xMax").as_float(), xmlNode.child(name.c_str()).attribute("yMax").as_float()); 248 | return ImRect(min, max); 249 | } 250 | 251 | ImVec2 LoadImVec2(const pugi::xml_node & xmlNode, const std::string & name) 252 | { 253 | float x = xmlNode.child(name.c_str()).attribute("x").as_float(); 254 | float y = xmlNode.child(name.c_str()).attribute("y").as_float(); 255 | return ImVec2(x, y); 256 | } 257 | 258 | ImColor LoadImColor(const pugi::xml_node & xmlNode, const std::string & name) 259 | { 260 | float r = xmlNode.child(name.c_str()).attribute("x").as_float(); 261 | float g = xmlNode.child(name.c_str()).attribute("y").as_float(); 262 | float b = xmlNode.child(name.c_str()).attribute("z").as_float(); 263 | float a = xmlNode.child(name.c_str()).attribute("w").as_float(); 264 | return ImColor(r, g, b, a); 265 | } 266 | 267 | float LoadFloat(const pugi::xml_node & xmlNode, const std::string & name) 268 | { 269 | return xmlNode.child(name.c_str()).attribute("data").as_float(); 270 | } 271 | 272 | double LoadDouble(const pugi::xml_node& xmlNode, const std::string& name) 273 | { 274 | return xmlNode.child(name.c_str()).attribute("data").as_double(); 275 | } 276 | 277 | std::string LoadString(const pugi::xml_node & xmlNode, const std::string & name) 278 | { 279 | return xmlNode.child(name.c_str()).attribute("data").as_string(); 280 | } 281 | 282 | int LoadInt(const pugi::xml_node & xmlNode, const std::string & name) 283 | { 284 | return xmlNode.child(name.c_str()).attribute("data").as_int(); 285 | } 286 | 287 | bool LoadBool(const pugi::xml_node & xmlNode, const std::string & name) 288 | { 289 | return xmlNode.child(name.c_str()).attribute("data").as_bool(); 290 | } -------------------------------------------------------------------------------- /core-nodes/FileDialog.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * File Dialog * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef FILEDIALOG_HPP 11 | #define FILEDIALOG_HPP 12 | 13 | #include 14 | #include "imgui.h" 15 | #include "imgui_internal.h" 16 | #include "imgui_stdlib.h" 17 | #include "pugixml.hpp" 18 | #include "Notifier.hpp" 19 | 20 | class FileDialog 21 | { 22 | public: 23 | enum class Type 24 | { 25 | SAVE, 26 | OPEN 27 | }; 28 | private: 29 | Type type = Type::OPEN; 30 | std::string fileFormat{ ".dxdt" }; 31 | std::filesystem::path fileName; 32 | std::filesystem::path directoryPath; 33 | std::filesystem::path resultPath; 34 | bool refresh; 35 | size_t currentIndex; 36 | std::vector currentFiles; 37 | std::vector currentDirectories; 38 | std::string title{ "" }; 39 | static const size_t bufferSize = 200; 40 | char buffer[bufferSize]; 41 | 42 | public: 43 | FileDialog() = default; 44 | virtual ~FileDialog() = default; 45 | 46 | void SetType(Type t) { type = t; } 47 | void SetFileName(std::string_view name) { fileName = name; } 48 | void SetDirectory(const std::filesystem::path& dir) { directoryPath = dir; } 49 | std::filesystem::path GetResultPath() const { return resultPath; } 50 | auto GetFileName() const { return fileName; } 51 | auto GetFileFormat() const { return fileFormat; } 52 | Type GetType() const { return type; } 53 | bool Draw(bool* open); 54 | }; 55 | 56 | void SaveImRect(pugi::xml_node& xmlNode, const std::string& name, const ImRect& rect); 57 | void SaveImVec2(pugi::xml_node& xmlNode, const std::string& name, const ImVec2& vec); 58 | void SaveImColor(pugi::xml_node& xmlNode, const std::string& name, const ImColor& color); 59 | void SaveFloat(pugi::xml_node& xmlNode, const std::string& name, float data); 60 | void SaveDouble(pugi::xml_node& xmlNode, const std::string& name, double data); 61 | void SaveString(pugi::xml_node& xmlNode, const std::string& name, const std::string& str); 62 | void SaveInt(pugi::xml_node& xmlNode, const std::string& name, int i); 63 | void SaveBool(pugi::xml_node& xmlNode, const std::string& name, bool b); 64 | 65 | ImRect LoadImRect(const pugi::xml_node& xmlNode, const std::string& name); 66 | ImVec2 LoadImVec2(const pugi::xml_node& xmlNode, const std::string& name); 67 | ImColor LoadImColor(const pugi::xml_node& xmlNode, const std::string& name); 68 | float LoadFloat(const pugi::xml_node& xmlNode, const std::string& name); 69 | double LoadDouble(const pugi::xml_node& xmlNode, const std::string& name); 70 | std::string LoadString(const pugi::xml_node& xmlNode, const std::string& name); 71 | int LoadInt(const pugi::xml_node& xmlNode, const std::string& name); 72 | bool LoadBool(const pugi::xml_node& xmlNode, const std::string& name); 73 | 74 | #endif /* FILEDIALOG_HPP */ -------------------------------------------------------------------------------- /core-nodes/GainNode.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Gain Node * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "GainNode.hpp" 11 | 12 | void GainNode::Build() 13 | { 14 | AddInput(CoreNodeInput("Input", PortType::In, PortDataType::Double)); 15 | AddOutput(CoreNodeOutput("Output", PortType::Out, PortDataType::Double)); 16 | BuildGeometry(); 17 | } 18 | 19 | void GainNode::DrawProperties(const std::vector& coreNodeVec) 20 | { 21 | ImGui::Text(GetLibName().c_str()); 22 | ImGui::Separator(); 23 | ImGui::Text("Outputs input times parameter."); 24 | ImGui::NewLine(); 25 | ImGui::Text("Parameters"); 26 | ImGui::Separator(); 27 | EditName(coreNodeVec); 28 | gain.Draw(modifFlag); 29 | } 30 | 31 | void GainNode::SaveProperties(pugi::xml_node& xmlNode) 32 | { 33 | SaveDouble(xmlNode, "gain", gain.Get()); 34 | } 35 | 36 | void GainNode::LoadProperties(pugi::xml_node& xmlNode) 37 | { 38 | gain.Set(LoadDouble(xmlNode, "gain")); 39 | } -------------------------------------------------------------------------------- /core-nodes/GainNode.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Gain Node * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef GAINNODE_HPP 11 | #define GAINNODE_HPP 12 | 13 | #include "CoreNode.hpp" 14 | 15 | class GainNode : public CoreNode 16 | { 17 | public: 18 | explicit GainNode(const std::string& uniqueName) : CoreNode(uniqueName, "Gain", NodeType::Generic, ImColor(0.2f, 0.3f, 0.6f, 0.0f)) {}; 19 | ~GainNode() override = default; 20 | 21 | void Build() override; 22 | void DrawProperties(const std::vector& coreNodeVec) override; 23 | 24 | void SaveProperties(pugi::xml_node& xmlNode) override; 25 | void LoadProperties(pugi::xml_node& xmlNode) override; 26 | private: 27 | NodeParamDouble gain{"gain", 1.0}; 28 | }; 29 | 30 | #endif /* GAINNODE_HPP */ -------------------------------------------------------------------------------- /core-nodes/MyApp.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * MyApp * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "MyApp.hpp" 11 | 12 | void MyApp::Update() 13 | { 14 | //TestBasic(); 15 | Dockspace(); 16 | DrawFileDialog(); 17 | ImGui::PushFont(fontLarge); 18 | Notifier::Draw(); 19 | ImGui::PopFont(); 20 | UndoRedoSave(); 21 | DrawSaveModal(); 22 | DrawAbout(); 23 | } 24 | 25 | void MyApp::SelectTab(const char* windowName) const 26 | { 27 | ImGuiWindow* window = ImGui::FindWindowByName(windowName); 28 | if (window == nullptr || window->DockNode == nullptr || window->DockNode->TabBar == nullptr) { return; } 29 | window->DockNode->TabBar->NextSelectedTabId = window->TabId; 30 | } 31 | 32 | MyApp::MyApp() : GuiApp("MyApp") 33 | { 34 | SetTitle("untitled"); 35 | coreDiagram = std::make_unique(); 36 | } 37 | 38 | void MyApp::Dockspace() 39 | { 40 | const ImGuiViewport* viewport = ImGui::GetMainViewport(); 41 | ImGui::SetNextWindowPos(viewport->Pos); 42 | ImGui::SetNextWindowSize(viewport->Size); 43 | ImGui::SetNextWindowViewport(viewport->ID); 44 | ImGui::SetNextWindowBgAlpha(0.0f); 45 | 46 | ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; 47 | window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; 48 | window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; 49 | 50 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 51 | ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); 52 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); 53 | 54 | ImGui::Begin("DockSpace", &openDockspace, window_flags); 55 | ImGui::PopStyleVar(3); 56 | 57 | if (ImGui::DockBuilderGetNode(ImGui::GetID("MyDockspace")) == nullptr || redock) 58 | { 59 | redock = false; 60 | ImGuiID dockspace_id = ImGui::GetID("MyDockspace"); 61 | ImGui::DockBuilderRemoveNode(dockspace_id); // Clear out existing layout 62 | ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); // Add empty node 63 | ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size); 64 | 65 | ImGuiID dock_main_id = dockspace_id; 66 | ImGuiID dock_id_left_top = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.20f, nullptr, &dock_main_id); 67 | ImGuiID dock_id_left_bottom = ImGui::DockBuilderSplitNode(dock_id_left_top, ImGuiDir_Down, 0.60f, nullptr, &dock_id_left_top); 68 | 69 | ImGui::DockBuilderDockWindow("Simulation", dock_id_left_top); 70 | ImGui::DockBuilderDockWindow("Library", dock_id_left_top); 71 | ImGui::DockBuilderDockWindow("Diagram", dock_main_id); 72 | ImGui::DockBuilderDockWindow("Properties", dock_id_left_bottom); 73 | ImGui::DockBuilderDockWindow("Explorer", dock_id_left_bottom); 74 | ImGui::DockBuilderFinish(dockspace_id); 75 | } 76 | 77 | ImGuiID dockspace_id = ImGui::GetID("MyDockspace"); 78 | ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode; 79 | ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); 80 | Menu(); 81 | ImGui::End(); 82 | 83 | ImGui::Begin("Simulation", nullptr, ImGuiWindowFlags_None); 84 | ImGui::Text("Text"); 85 | ImGui::Text("(%.1f FPS)", ImGui::GetIO().Framerate); 86 | ImGui::End(); 87 | 88 | ImGui::Begin("Library", nullptr, ImGuiWindowFlags_None); 89 | coreDiagram->DrawLibrary(); 90 | ImGui::End(); 91 | 92 | ImGui::Begin("Explorer", nullptr, ImGuiWindowFlags_None); 93 | coreDiagram->DrawExplorer(); 94 | ImGui::End(); 95 | 96 | ImGui::Begin("Diagram", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); 97 | coreDiagram->Update(); 98 | ImGui::End(); 99 | 100 | ImGui::Begin("Properties", nullptr, ImGuiWindowFlags_None); 101 | coreDiagram->DrawProperties(); 102 | ImGui::End(); 103 | 104 | if (initialSetup == false) 105 | { 106 | SelectTab("Simulation"); 107 | initialSetup = true; 108 | ImGui::SetWindowFocus("Diagram"); 109 | ResetDocDeque(); 110 | } 111 | } 112 | 113 | void MyApp::Menu() 114 | { 115 | if (ImGui::BeginMenuBar()) 116 | { 117 | MenuFile(); 118 | MenuView(); 119 | MenuHelp(); 120 | ImGui::EndMenuBar(); 121 | } 122 | } 123 | 124 | void MyApp::MenuFile() 125 | { 126 | if (ImGui::BeginMenu("File")) 127 | { 128 | if (ImGui::MenuItem(u8"\ue873 New", nullptr, false, true)) 129 | { 130 | if (GetAsterisk() == true) 131 | { 132 | openSaveModal = true; 133 | stateSaveModal = 1; 134 | } 135 | else 136 | { 137 | NewProject(); 138 | } 139 | } 140 | if (ImGui::MenuItem(u8"\ueaf3 Open", nullptr, false, true)) 141 | { 142 | if (GetAsterisk() == true) 143 | { 144 | openSaveModal = true; 145 | stateSaveModal = 2; 146 | } 147 | else 148 | { 149 | OpenProject(); 150 | } 151 | } 152 | ImGui::Separator(); 153 | if (ImGui::MenuItem(u8"\ue161 Save", nullptr, false, true)) 154 | { 155 | SaveProject(); 156 | } 157 | if (ImGui::MenuItem(u8"\ueb60 Save As...", nullptr, false, true)) 158 | { 159 | SaveProject(true); 160 | } 161 | ImGui::Separator(); 162 | if (ImGui::MenuItem(u8"\ue9ba Exit", nullptr, false, true)) 163 | { 164 | attemptToClose = true; 165 | } 166 | ImGui::EndMenu(); 167 | } 168 | 169 | // Exit 170 | if (attemptToClose == true) // Exit, Window X, alt+f4. 171 | { 172 | attemptToClose = false; 173 | if (GetAsterisk() == true) 174 | { 175 | openSaveModal = true; 176 | stateSaveModal = 3; 177 | } 178 | else 179 | { 180 | timeToClose = true; 181 | } 182 | } 183 | } 184 | 185 | void MyApp::MenuView() 186 | { 187 | if (ImGui::BeginMenu("View")) 188 | { 189 | if (ImGui::MenuItem(u8"\ue871 Redock", nullptr, false, true)) 190 | { 191 | redock = true; 192 | } 193 | ImGui::EndMenu(); 194 | } 195 | } 196 | 197 | void MyApp::MenuHelp() 198 | { 199 | if (ImGui::BeginMenu("Help")) 200 | { 201 | if (ImGui::MenuItem(u8"\ue88e About", nullptr, false, true)) 202 | { 203 | openAbout = true; 204 | } 205 | ImGui::EndMenu(); 206 | } 207 | } 208 | 209 | void MyApp::DrawFileDialog() 210 | { 211 | if (fileDialog.Draw(&fileDialogOpen)) 212 | { 213 | if (fileDialog.GetType() == FileDialog::Type::OPEN) 214 | { 215 | LoadFromFile(); 216 | } 217 | else if (fileDialog.GetType() == FileDialog::Type::SAVE) 218 | { 219 | SaveToFile(fileDialog.GetFileName().string(), fileDialog.GetResultPath().string()); 220 | } 221 | } 222 | } 223 | 224 | void MyApp::NewProject() 225 | { 226 | SetTitle("new"); 227 | hasFile = false; 228 | SetAsterisk(false); 229 | coreDiagram = std::make_unique(); 230 | ResetDocDeque(); 231 | Notifier::Add(Notif(Notif::Type::INFO, "New")); 232 | } 233 | 234 | void MyApp::OpenProject() 235 | { 236 | fileDialogOpen = true; 237 | fileDialog.SetType(FileDialog::Type::OPEN); 238 | fileDialog.SetFileName(fileDialog.GetFileFormat()); 239 | fileDialog.SetDirectory(hasFile ? filePath.parent_path() : std::filesystem::current_path()); 240 | } 241 | 242 | void MyApp::DrawSaveModal() 243 | { 244 | if (openSaveModal == true) 245 | { 246 | ImGui::OpenPopup("Save?"); 247 | openSaveModal = false; 248 | } 249 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 250 | if (ImGui::BeginPopupModal("Save?", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize)) 251 | { 252 | ImGui::Text("Current project has unsaved progress.\nDo you want to save the project?"); 253 | ImGui::Separator(); 254 | if (ImGui::Button("Yes", ImVec2(80, 0))) 255 | { 256 | SaveProject(); 257 | ImGui::CloseCurrentPopup(); 258 | } 259 | ImGui::SameLine(); 260 | if (ImGui::Button("No", ImVec2(80, 0))) 261 | { 262 | if (stateSaveModal == 1) 263 | { 264 | NewProject(); 265 | } 266 | else if (stateSaveModal == 2) 267 | { 268 | OpenProject(); 269 | } 270 | else if (stateSaveModal == 3) 271 | { 272 | timeToClose = true; 273 | } 274 | stateSaveModal = 0; 275 | ImGui::CloseCurrentPopup(); 276 | } 277 | ImGui::SameLine(); 278 | if (ImGui::Button("Cancel", ImVec2(80, 0))) 279 | { 280 | ImGui::CloseCurrentPopup(); 281 | } 282 | ImGui::EndPopup(); 283 | } 284 | } 285 | 286 | pugi::xml_document MyApp::CreateDoc() const 287 | { 288 | pugi::xml_document doc; 289 | auto declarationNode = doc.append_child(pugi::node_declaration); 290 | declarationNode.append_attribute("version") = "1.0"; 291 | declarationNode.append_attribute("encoding") = "ISO-8859-1"; 292 | declarationNode.append_attribute("standalone") = "yes"; 293 | 294 | auto root = doc.append_child("core-nodes"); 295 | root.append_attribute("version").set_value("v0.1.0"); 296 | auto sim = root.append_child("simulation"); 297 | sim.append_attribute("solver").set_value(1); 298 | sim.append_attribute("sampleTime").set_value(0.01); 299 | sim.append_attribute("stopTime").set_value(5.0); 300 | sim.append_attribute("speed").set_value("realTime"); 301 | coreDiagram->Save(root); 302 | return doc; 303 | } 304 | 305 | void MyApp::SaveProject(bool saveAs) 306 | { 307 | if (saveAs == true || hasFile == false) 308 | { 309 | fileDialogOpen = true; 310 | fileDialog.SetType(FileDialog::Type::SAVE); 311 | fileDialog.SetFileName("untitled"); 312 | fileDialog.SetDirectory(hasFile ? filePath.parent_path() : std::filesystem::current_path()); 313 | } 314 | else 315 | { 316 | SaveToFile(GetTitle(), filePath.string()); 317 | } 318 | } 319 | 320 | void MyApp::SaveToFile(const std::string& fName, const std::string& fPath) 321 | { 322 | auto doc = CreateDoc(); 323 | if (doc.save_file(fPath.c_str(), PUGIXML_TEXT(" "))) 324 | { 325 | filePath = fPath; 326 | hasFile = true; 327 | SetTitle(fName); 328 | SetAsterisk(false); 329 | iSavedDoc = iCurrentDoc; 330 | Notifier::Add(Notif(Notif::Type::SUCCESS, "Saved")); 331 | } 332 | else 333 | { 334 | Notifier::Add(Notif(Notif::Type::ERROR, "Save failed")); 335 | } 336 | } 337 | 338 | void MyApp::LoadDoc(const pugi::xml_document* doc) 339 | { 340 | pugi::xml_node root = doc->document_element(); 341 | // TODO read simulation related parameters here. 342 | coreDiagram = std::make_unique(); 343 | coreDiagram->Load(root); 344 | } 345 | 346 | void MyApp::LoadFromFile() 347 | { 348 | auto fPath = fileDialog.GetResultPath().string(); 349 | auto fNameWFormat = fileDialog.GetFileName().string(); 350 | auto fNameWoFormat = fNameWFormat.substr(0, fNameWFormat.rfind(".")); 351 | pugi::xml_document doc; 352 | pugi::xml_parse_result result = doc.load_file(fPath.c_str(), pugi::parse_default | pugi::parse_declaration); 353 | if (result) 354 | { 355 | // TODO check version. 356 | LoadDoc(&doc); 357 | ResetDocDeque(); 358 | filePath = fileDialog.GetResultPath(); 359 | hasFile = true; 360 | SetTitle(fNameWoFormat); 361 | SetAsterisk(false); 362 | Notifier::Add(Notif(Notif::Type::SUCCESS, "Loaded")); 363 | } 364 | else 365 | { 366 | Notifier::Add(Notif(Notif::Type::ERROR, "Load failed")); 367 | } 368 | } 369 | 370 | void MyApp::UndoRedoSave() 371 | { 372 | if (ImGui::IsAnyItemActive() == true) 373 | { 374 | return; 375 | } 376 | 377 | // If project modified, add doc. TODO: other modifications 378 | if (coreDiagram->GetModifFlag() == true) 379 | { 380 | if (iCurrentDoc < docs.size() - 1) 381 | { 382 | docs.erase(docs.begin() + iCurrentDoc + 1, docs.end()); 383 | } 384 | docs.emplace_back(CreateDoc()); 385 | iCurrentDoc += 1; 386 | coreDiagram->ResetModifFlag(); 387 | SetAsterisk(true); 388 | if (docs.size() > maxSavedDoc) 389 | { 390 | docs.pop_front(); 391 | iCurrentDoc -= 1; 392 | iSavedDoc -= 1; 393 | } 394 | return; 395 | } 396 | 397 | // Undo 398 | const auto& io = ImGui::GetIO(); 399 | if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Z)) 400 | { 401 | if (iCurrentDoc > 0) 402 | { 403 | auto doc = &(docs[iCurrentDoc - 1]); 404 | iCurrentDoc -= 1; 405 | LoadDoc(doc); 406 | if (iCurrentDoc == iSavedDoc) 407 | { 408 | SetAsterisk(false); 409 | } 410 | else 411 | { 412 | SetAsterisk(true); 413 | } 414 | Notifier::Add(Notif(Notif::Type::SUCCESS, "Undo")); 415 | return; 416 | } 417 | Notifier::Add(Notif(Notif::Type::INFO, "Cannot undo")); 418 | } 419 | 420 | // Redo 421 | if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Y)) 422 | { 423 | if (iCurrentDoc < docs.size() - 1) 424 | { 425 | auto doc = &(docs[iCurrentDoc + 1]); 426 | iCurrentDoc += 1; 427 | LoadDoc(doc); 428 | if (iCurrentDoc == iSavedDoc) 429 | { 430 | SetAsterisk(false); 431 | } 432 | else 433 | { 434 | SetAsterisk(true); 435 | } 436 | Notifier::Add(Notif(Notif::Type::SUCCESS, "Redo")); 437 | return; 438 | } 439 | Notifier::Add(Notif(Notif::Type::INFO, "Cannot redo")); 440 | } 441 | 442 | // Save 443 | if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S) && (iSavedDoc != iCurrentDoc || hasFile == false)) 444 | { 445 | SaveProject(); 446 | } 447 | } 448 | 449 | void MyApp::ResetDocDeque() 450 | { 451 | docs.clear(); 452 | iCurrentDoc = 0; 453 | iSavedDoc = 0; 454 | docs.emplace_back(CreateDoc()); 455 | } 456 | 457 | void MyApp::DrawAbout() 458 | { 459 | if (openAbout == true) 460 | { 461 | ImGui::OpenPopup("About"); 462 | openAbout = false; 463 | } 464 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 465 | if (ImGui::BeginPopupModal("About", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize)) 466 | { 467 | ImGui::Separator(); 468 | ImGui::Text(u8"\ue90c 2023 OnurAKIN."); 469 | ImGui::Text("Licensed under the MIT License."); 470 | ImGui::Separator(); 471 | ImGui::Text(u8"\ue86f https://github.com/onurae"); 472 | ImGui::Separator(); 473 | if (ImGui::Button("OK", ImVec2(200, 0))) 474 | { 475 | ImGui::CloseCurrentPopup(); 476 | } 477 | ImGui::EndPopup(); 478 | } 479 | } 480 | 481 | void MyApp::TestBasic() const 482 | { 483 | ImGui::Begin("Basic", nullptr, ImGuiWindowFlags_AlwaysAutoResize); 484 | ImGui::Text("Hello, world %d", 123); 485 | ImGui::Text("This is RobotoMedium %d", 123); // the first loaded font gets used by default 486 | ImGui::PushFont(fontRobotoRegular); // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime 487 | ImGui::Text("This is RobotoRegular %d", 123); 488 | ImGui::PopFont(); 489 | ImGui::Text(u8"\uefe1 Hello % d", 123); 490 | ImGui::Text(u8"\uea16 Hello % d", 123); 491 | 492 | ImGui::PushFont(fontLarge); 493 | ImGui::Text(u8"\uf011 This is RobotoMedium24"); 494 | ImGui::PopFont(); 495 | 496 | ImGui::Spacing(); 497 | static float f = 0; 498 | if (ImGui::Button(u8"\ue1c4 Run")) { f = 0.0f; } 499 | ImGui::SliderFloat("float", &f, 0.0f, 1.0f); 500 | ImGui::End(); 501 | } 502 | -------------------------------------------------------------------------------- /core-nodes/MyApp.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * MyApp * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "gui-app-template/GuiApp.hpp" 11 | #include "CoreDiagram.hpp" 12 | #include 13 | #include 14 | #include 15 | 16 | class MyApp : public GuiApp 17 | { 18 | public: 19 | MyApp(); 20 | ~MyApp() final = default; 21 | 22 | void Update() override; 23 | void TestBasic() const; 24 | 25 | private: 26 | bool initialSetup = false; 27 | void SelectTab(const char* windowName) const; 28 | std::string version{ "v0.1.0" }; 29 | std::unique_ptr coreDiagram; 30 | 31 | bool openDockspace = true; 32 | bool redock = false; 33 | void Dockspace(); 34 | 35 | void Menu(); 36 | void MenuFile(); 37 | void MenuView(); 38 | void MenuHelp(); 39 | 40 | FileDialog fileDialog; 41 | bool fileDialogOpen = false; 42 | void DrawFileDialog(); 43 | 44 | bool hasFile = false; 45 | std::filesystem::path filePath; 46 | void NewProject(); 47 | void OpenProject(); 48 | bool openSaveModal = false; 49 | int stateSaveModal = 0; // 1: from new, 2: from open, 3: from exit. 50 | void DrawSaveModal(); 51 | pugi::xml_document CreateDoc() const; 52 | void SaveProject(bool saveAs = false); 53 | void SaveToFile(const std::string& fName, const std::string& fPath); 54 | void LoadDoc(const pugi::xml_document* doc); 55 | void LoadFromFile(); 56 | 57 | std::deque docs; 58 | int iCurrentDoc{ 0 }; 59 | int iSavedDoc{ 0 }; 60 | const int maxSavedDoc{ 20 }; 61 | void UndoRedoSave(); 62 | void ResetDocDeque(); 63 | 64 | bool openAbout = false; 65 | void DrawAbout(); 66 | }; -------------------------------------------------------------------------------- /core-nodes/Notifier.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Notifier * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "Notifier.hpp" 11 | 12 | Notif::Status Notif::GetStatus() const 13 | { 14 | const auto elapsedTime = ImGui::GetTime() - notifCreateTime; // [s] 15 | if (elapsedTime > fadeInTime + onTime + fadeOutTime) { return Status::OFF; } 16 | else if (elapsedTime > fadeInTime + onTime) { return Status::FADEOUT; } 17 | else if (elapsedTime > fadeInTime) { return Status::ON; } 18 | else { return Status::FADEIN; } 19 | } 20 | 21 | std::string Notif::GetIcon() const 22 | { 23 | if (type == Type::SUCCESS) { return std::string(u8"\ue86c"); } 24 | else if (type == Type::WARNING) { return std::string(u8"\ue002"); } 25 | else if (type == Type::ERROR) { return std::string(u8"\ue000"); } 26 | else if (type == Type::INFO) { return std::string(u8"\ue88e"); } 27 | else { return ""; } 28 | } 29 | 30 | std::string Notif::GetTypeName() const 31 | { 32 | if (type == Type::SUCCESS) { return "Success"; } 33 | else if (type == Type::WARNING) { return "Warning"; } 34 | else if (type == Type::ERROR) { return "Error"; } 35 | else if (type == Type::INFO) { return "Info"; } 36 | else { return ""; } 37 | } 38 | 39 | float Notif::GetFadeValue() const 40 | { 41 | const auto status = GetStatus(); 42 | const auto elapsedTime = static_cast(ImGui::GetTime() - notifCreateTime); // [s] 43 | if (status == Status::FADEIN) 44 | { 45 | return (elapsedTime / fadeInTime) * opacity; 46 | } 47 | else if (status == Status::FADEOUT) 48 | { 49 | return (1.0f - ((elapsedTime - fadeInTime - onTime) / fadeOutTime)) * opacity; 50 | } 51 | return 1.0f * opacity; 52 | } 53 | 54 | ImColor Notif::GetColor() const 55 | { 56 | if (type == Type::SUCCESS) { return ImColor(0.0f, 0.5f, 0.0f, 1.0f); } 57 | else if (type == Type::WARNING) { return ImColor(0.5f, 0.5f, 0.0f, 1.0f); } 58 | else if (type == Type::ERROR) { return ImColor(0.5f, 0.0f, 0.0f, 1.0f); } 59 | else if (type == Type::INFO) { return ImColor(0.0f, 0.3f, 0.5f, 1.0f); } 60 | else { return ImColor(1.0f, 1.0f, 1.0f, 1.0f); } 61 | } 62 | 63 | Notif::Notif(Type type, const std::string& title, const std::string& content, float onTime) : 64 | type(type), title(title), content(content), onTime(onTime) 65 | { 66 | notifCreateTime = static_cast(ImGui::GetTime()); 67 | } 68 | 69 | void Notifier::DrawNotifications() 70 | { 71 | float height = 0.0f; 72 | for (int i = 0; i < notifs.size(); i++) 73 | { 74 | auto notif = ¬ifs[i]; 75 | if (notif->GetStatus() == Notif::Status::OFF) 76 | { 77 | RemoveNotif(i); 78 | continue; 79 | } 80 | std::string notifName = "Notif" + std::to_string(i); 81 | const auto fadeValue = notif->GetFadeValue(); 82 | ImGui::SetNextWindowBgAlpha(fadeValue); 83 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, rounding); 84 | ImGui::PushStyleColor(ImGuiCol_WindowBg, backgroundColor); 85 | const auto rightCorner = ImVec2(ImGui::GetMainViewport()->Pos.x + ImGui::GetMainViewport()->Size.x, ImGui::GetMainViewport()->Pos.y + ImGui::GetMainViewport()->Size.y); 86 | ImGui::SetNextWindowPos(ImVec2(rightCorner.x - xPadding, rightCorner.y - yPadding - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f)); 87 | ImGui::Begin(notifName.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing); 88 | ImGui::PushTextWrapPos(ImGui::GetMainViewport()->Size.x * wrapRatio); 89 | const auto icon = notif->GetIcon(); 90 | auto iconColor = notif->GetColor(); 91 | iconColor.Value.w = fadeValue; 92 | ImGui::TextColored(iconColor, icon.c_str()); 93 | ImGui::SameLine(); 94 | if (const auto title = notif->GetTitle(); title.empty() == false) 95 | { 96 | ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), title.c_str()); 97 | } 98 | else if (const auto typeName = notif->GetTypeName(); typeName.empty() == false) 99 | { 100 | ImGui::TextColored(notif->GetColor(), typeName.c_str()); 101 | } 102 | if (const auto content = notif->GetContent(); content.empty() == false) 103 | { 104 | ImGui::Separator(); 105 | ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), content.c_str()); 106 | } 107 | ImGui::PopTextWrapPos(); 108 | height += ImGui::GetWindowHeight() + yMessagePadding; 109 | ImGui::End(); 110 | ImGui::PopStyleVar(); 111 | ImGui::PopStyleColor(); 112 | } 113 | heightNotifs = height; 114 | } 115 | 116 | void Notifier::AddNotif(const Notif & notif) 117 | { 118 | if (heightNotifs >= ImGui::GetMainViewport()->Size.y * 0.8f) 119 | { 120 | RemoveNotif(0); 121 | } 122 | notifs.push_back(notif); 123 | } 124 | -------------------------------------------------------------------------------- /core-nodes/Notifier.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Notifier * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef NOTIFIER_HPP 11 | #define NOTIFIER_HPP 12 | 13 | #include "imgui.h" 14 | #include 15 | #include 16 | 17 | class Notif 18 | { 19 | public: 20 | enum class Status 21 | { 22 | FADEIN, 23 | ON, 24 | FADEOUT, 25 | OFF 26 | }; 27 | enum class Type 28 | { 29 | NONE, 30 | SUCCESS, 31 | WARNING, 32 | ERROR, 33 | INFO 34 | }; 35 | Notif(Type type, const std::string& title, const std::string& content = "", float onTime = 3.0f); 36 | virtual ~Notif() = default; 37 | 38 | Status GetStatus() const; 39 | std::string GetIcon() const; 40 | std::string GetTitle() const { return title; } 41 | std::string GetContent() const { return content; } 42 | std::string GetTypeName() const; 43 | float GetFadeValue() const; 44 | ImColor GetColor() const; 45 | 46 | private: 47 | Type type = Type::NONE; 48 | std::string title{ "" }; 49 | std::string content{ "" }; 50 | float fadeInTime{ 0.150f }; 51 | float onTime{ 3.0f }; 52 | float fadeOutTime{ 0.150f }; 53 | float opacity{ 1.0f }; 54 | float notifCreateTime{ 0.0f }; 55 | }; 56 | 57 | class Notifier 58 | { 59 | public: 60 | Notifier(const Notifier&) = delete; 61 | Notifier& operator=(const Notifier&) = delete; 62 | virtual ~Notifier() = default; 63 | static Notifier& Get() 64 | { 65 | static Notifier instance; 66 | return instance; 67 | } 68 | static void Add(const Notif& notif) { Get().AddNotif(notif); } 69 | static void Draw() { Get().DrawNotifications(); } 70 | void DrawNotifications(); 71 | 72 | private: 73 | Notifier() = default; 74 | void AddNotif(const Notif& notif); 75 | float xPadding{ 30.0f }; 76 | float yPadding{ 30.0f }; 77 | float yMessagePadding{ 10.0f }; 78 | float wrapRatio{ 0.25f }; 79 | float rounding = 8.0f; 80 | ImVec4 backgroundColor = ImVec4(0.886f, 0.929f, 0.969f, 0.4f); 81 | std::vector notifs; 82 | float heightNotifs{ 0.0f }; 83 | void RemoveNotif(int i) { notifs.erase(notifs.begin() + i); } 84 | }; 85 | 86 | #endif /* NOTIFIER_HPP */ -------------------------------------------------------------------------------- /core-nodes/TestNode.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Test Node * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #include "TestNode.hpp" 11 | 12 | void TestNode::Build() 13 | { 14 | AddInput(CoreNodeInput("Input1", PortType::In, PortDataType::Float)); 15 | AddInput(CoreNodeInput("Input2", PortType::In, PortDataType::Double)); 16 | AddInput(CoreNodeInput("Input3", PortType::In, PortDataType::Int)); 17 | AddInput(CoreNodeInput("Input4", PortType::In, PortDataType::Double)); 18 | AddOutput(CoreNodeOutput("Output1", PortType::Out, PortDataType::Int)); 19 | AddOutput(CoreNodeOutput("Output2", PortType::Out, PortDataType::Double)); 20 | AddOutput(CoreNodeOutput("Output3", PortType::Out, PortDataType::Generic)); 21 | BuildGeometry(); 22 | } 23 | 24 | void TestNode::DrawProperties(const std::vector& coreNodeVec) 25 | { 26 | ImGui::Text(GetLibName().c_str()); 27 | ImGui::Separator(); 28 | ImGui::Text("This is a test module explanation."); 29 | ImGui::NewLine(); 30 | ImGui::Text("Parameters"); 31 | ImGui::Separator(); 32 | EditName(coreNodeVec); 33 | parameter1.Draw(modifFlag); 34 | parameter2.Draw(modifFlag); 35 | } 36 | 37 | void TestNode::SaveProperties(pugi::xml_node& xmlNode) 38 | { 39 | SaveDouble(xmlNode, "parameter1", parameter1.Get()); 40 | SaveDouble(xmlNode, "parameter2", parameter2.Get()); 41 | } 42 | 43 | void TestNode::LoadProperties(pugi::xml_node& xmlNode) 44 | { 45 | parameter1.Set(LoadDouble(xmlNode, "parameter1")); 46 | parameter2.Set(LoadDouble(xmlNode, "parameter2")); 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /core-nodes/TestNode.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * * 3 | * Test Node * 4 | * * 5 | * Copyright (c) 2023 Onur AKIN * 6 | * Licensed under the MIT License. * 7 | * * 8 | ******************************************************************************************/ 9 | 10 | #ifndef TESTNODE_HPP 11 | #define TESTNODE_HPP 12 | 13 | #include "CoreNode.hpp" 14 | 15 | class TestNode : public CoreNode 16 | { 17 | public: 18 | explicit TestNode(const std::string& uniqueName) : CoreNode(uniqueName, "Test", NodeType::Generic, ImColor(0.2f, 0.3f, 0.6f, 0.0f)) {}; 19 | ~TestNode() override = default; 20 | 21 | void Build() override; 22 | void DrawProperties(const std::vector& coreNodeVec) override; 23 | 24 | void SaveProperties(pugi::xml_node& xmlNode) override; 25 | void LoadProperties(pugi::xml_node& xmlNode) override; 26 | private: 27 | NodeParamDouble parameter1{ "parameter1", 1.234 }; 28 | NodeParamDouble parameter2{ "parameter2", 1.234 }; 29 | }; 30 | 31 | #endif /* TESTNODE_HPP */ -------------------------------------------------------------------------------- /core-nodes/main.cpp: -------------------------------------------------------------------------------- 1 | #include "MyApp.hpp" 2 | 3 | int main() 4 | { 5 | MyApp myApp; 6 | myApp.Run(); 7 | return 0; 8 | } -------------------------------------------------------------------------------- /images/core-nodes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onurae/core-nodes/860b03c7c04b28b01d8a8c040d5e3b02d0a8bb1c/images/core-nodes.gif -------------------------------------------------------------------------------- /untitled.dxdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | --------------------------------------------------------------------------------