From 88fca7b128b3c9a8b2fead8587fea6ac8a5cf83b Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Fri, 24 Apr 2026 21:55:43 +0200 Subject: [PATCH] feat(shaders_lab): visual node editor (imgui-node-editor) + multi-source - cpp/vendor/imgui-node-editor: vendorized thedmd/imgui-node-editor v0.9.4 - cpp/functions/gfx/dag_node_editor: new visual pipeline editor replacing dag_panel - DagStep: source_ids[4] + editor_pos + editor_uid (multi-input support) - DagNodeDef: num_inputs explicit; circle reclassified as Op - dag_compile: N inputs per node, topological ordering preserved - main: use node editor; destroy on shutdown - patch imgui_extra_math.inl: guard operator*(float, ImVec2) for imgui >= 18955 Co-Authored-By: Claude Sonnet 4.6 --- cpp/CMakeLists.txt | 11 + cpp/apps/shaders_lab/CMakeLists.txt | 2 + cpp/apps/shaders_lab/main.cpp | 4 +- cpp/functions/gfx/dag_catalog.cpp | 29 +- cpp/functions/gfx/dag_compile.cpp | 59 +- cpp/functions/gfx/dag_node_editor.cpp | 414 ++ cpp/functions/gfx/dag_node_editor.h | 17 + cpp/functions/gfx/dag_node_editor.md | 43 + cpp/functions/gfx/dag_panel.cpp | 8 +- cpp/functions/gfx/dag_types.h | 6 +- .../.github/workflows/build.yml | 61 + cpp/vendor/imgui-node-editor/.gitignore | 11 + cpp/vendor/imgui-node-editor/LICENSE | 21 + cpp/vendor/imgui-node-editor/crude_json.cpp | 890 +++ cpp/vendor/imgui-node-editor/crude_json.h | 250 + .../imgui-node-editor/docs/CHANGELOG.txt | 289 + cpp/vendor/imgui-node-editor/docs/README.md | 169 + cpp/vendor/imgui-node-editor/docs/TODO.md | 14 + .../imgui-node-editor/examples/CMakeLists.txt | 134 + .../examples/application/CMakeLists.txt | 109 + .../application/include/application.h | 57 + .../application/source/application.cpp | 244 + .../examples/application/source/config.h.in | 4 + .../application/source/entry_point.cpp | 21 + .../application/source/imgui_extra_keys.h | 65 + .../application/source/imgui_impl_dx11.cpp | 681 ++ .../application/source/imgui_impl_dx11.h | 29 + .../application/source/imgui_impl_glfw.cpp | 382 ++ .../application/source/imgui_impl_glfw.h | 36 + .../application/source/imgui_impl_opengl3.cpp | 947 +++ .../application/source/imgui_impl_opengl3.h | 66 + .../source/imgui_impl_opengl3_loader.h | 810 +++ .../application/source/imgui_impl_win32.cpp | 462 ++ .../application/source/imgui_impl_win32.h | 37 + .../examples/application/source/platform.h | 60 + .../application/source/platform_glfw.cpp | 287 + .../application/source/platform_win32.cpp | 313 + .../examples/application/source/renderer.h | 32 + .../application/source/renderer_dx11.cpp | 195 + .../application/source/renderer_ogl3.cpp | 208 + .../examples/application/source/setup.h | 98 + .../examples/application/support/Icon.icns | Bin 0 -> 172448 bytes .../examples/application/support/Icon.ico | Bin 0 -> 39257 bytes .../examples/application/support/Icon.png | Bin 0 -> 52666 bytes .../application/support/Info.plist.in | 41 + .../application/support/Resource.rc.in | 3 + .../basic-interaction-example/CMakeLists.txt | 3 + .../basic-interaction-example.cpp | 216 + .../blueprints-example/CMakeLists.txt | 9 + .../blueprints-example/blueprints-example.cpp | 1835 +++++ .../data/BlueprintBackground.png | Bin 0 -> 5513 bytes .../data/ic_restore_white_24dp.png | Bin 0 -> 332 bytes .../data/ic_save_white_24dp.png | Bin 0 -> 168 bytes .../blueprints-example/utilities/builders.cpp | 310 + .../blueprints-example/utilities/builders.h | 81 + .../blueprints-example/utilities/drawing.cpp | 252 + .../blueprints-example/utilities/drawing.h | 12 + .../blueprints-example/utilities/widgets.cpp | 16 + .../blueprints-example/utilities/widgets.h | 13 + .../examples/canvas-example/CMakeLists.txt | 5 + .../canvas-example/canvas-example.cpp | 251 + .../examples/data/Cuprum-Bold.ttf | Bin 0 -> 96364 bytes .../examples/data/Cuprum-OFL.txt | 93 + .../examples/data/Oswald-OFL.txt | 93 + .../examples/data/Oswald-Regular.ttf | Bin 0 -> 91400 bytes .../examples/data/Play-OFL.txt | 93 + .../examples/data/Play-Regular.ttf | Bin 0 -> 183852 bytes .../examples/simple-example/CMakeLists.txt | 3 + .../simple-example/simple-example.cpp | 63 + .../examples/widgets-example/CMakeLists.txt | 3 + .../widgets-example/widgets-example.cpp | 432 ++ .../imgui-node-editor/imgui_bezier_math.h | 144 + .../imgui-node-editor/imgui_bezier_math.inl | 675 ++ cpp/vendor/imgui-node-editor/imgui_canvas.cpp | 574 ++ cpp/vendor/imgui-node-editor/imgui_canvas.h | 273 + .../imgui-node-editor/imgui_extra_math.h | 77 + .../imgui-node-editor/imgui_extra_math.inl | 195 + .../imgui-node-editor/imgui_node_editor.cpp | 5874 +++++++++++++++++ .../imgui-node-editor/imgui_node_editor.h | 530 ++ .../imgui_node_editor_api.cpp | 762 +++ .../imgui_node_editor_internal.h | 1560 +++++ .../imgui_node_editor_internal.inl | 65 + .../misc/cmake-modules/FindScopeGuard.cmake | 17 + .../misc/cmake-modules/Findgl3w.cmake | 17 + .../misc/cmake-modules/Findimgui.cmake | 17 + .../cmake-modules/Findimgui_node_editor.cmake | 49 + .../misc/cmake-modules/Findstb_image.cmake | 17 + .../imgui-node-editor/misc/crude_json.natvis | 18 + .../misc/imgui_node_editor.natvis | 64 + 89 files changed, 22289 insertions(+), 41 deletions(-) create mode 100644 cpp/functions/gfx/dag_node_editor.cpp create mode 100644 cpp/functions/gfx/dag_node_editor.h create mode 100644 cpp/functions/gfx/dag_node_editor.md create mode 100644 cpp/vendor/imgui-node-editor/.github/workflows/build.yml create mode 100644 cpp/vendor/imgui-node-editor/.gitignore create mode 100644 cpp/vendor/imgui-node-editor/LICENSE create mode 100644 cpp/vendor/imgui-node-editor/crude_json.cpp create mode 100644 cpp/vendor/imgui-node-editor/crude_json.h create mode 100644 cpp/vendor/imgui-node-editor/docs/CHANGELOG.txt create mode 100644 cpp/vendor/imgui-node-editor/docs/README.md create mode 100644 cpp/vendor/imgui-node-editor/docs/TODO.md create mode 100644 cpp/vendor/imgui-node-editor/examples/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/application/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/application/include/application.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/application.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/config.h.in create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/entry_point.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_extra_keys.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3_loader.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/platform.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/platform_glfw.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/platform_win32.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/renderer.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/renderer_dx11.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/renderer_ogl3.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/application/source/setup.h create mode 100644 cpp/vendor/imgui-node-editor/examples/application/support/Icon.icns create mode 100644 cpp/vendor/imgui-node-editor/examples/application/support/Icon.ico create mode 100644 cpp/vendor/imgui-node-editor/examples/application/support/Icon.png create mode 100644 cpp/vendor/imgui-node-editor/examples/application/support/Info.plist.in create mode 100644 cpp/vendor/imgui-node-editor/examples/application/support/Resource.rc.in create mode 100644 cpp/vendor/imgui-node-editor/examples/basic-interaction-example/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/basic-interaction-example/basic-interaction-example.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/blueprints-example.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/data/BlueprintBackground.png create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/data/ic_restore_white_24dp.png create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/data/ic_save_white_24dp.png create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.h create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.h create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.h create mode 100644 cpp/vendor/imgui-node-editor/examples/canvas-example/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/canvas-example/canvas-example.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/data/Cuprum-Bold.ttf create mode 100644 cpp/vendor/imgui-node-editor/examples/data/Cuprum-OFL.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/data/Oswald-OFL.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/data/Oswald-Regular.ttf create mode 100644 cpp/vendor/imgui-node-editor/examples/data/Play-OFL.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/data/Play-Regular.ttf create mode 100644 cpp/vendor/imgui-node-editor/examples/simple-example/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/simple-example/simple-example.cpp create mode 100644 cpp/vendor/imgui-node-editor/examples/widgets-example/CMakeLists.txt create mode 100644 cpp/vendor/imgui-node-editor/examples/widgets-example/widgets-example.cpp create mode 100644 cpp/vendor/imgui-node-editor/imgui_bezier_math.h create mode 100644 cpp/vendor/imgui-node-editor/imgui_bezier_math.inl create mode 100644 cpp/vendor/imgui-node-editor/imgui_canvas.cpp create mode 100644 cpp/vendor/imgui-node-editor/imgui_canvas.h create mode 100644 cpp/vendor/imgui-node-editor/imgui_extra_math.h create mode 100644 cpp/vendor/imgui-node-editor/imgui_extra_math.inl create mode 100644 cpp/vendor/imgui-node-editor/imgui_node_editor.cpp create mode 100644 cpp/vendor/imgui-node-editor/imgui_node_editor.h create mode 100644 cpp/vendor/imgui-node-editor/imgui_node_editor_api.cpp create mode 100644 cpp/vendor/imgui-node-editor/imgui_node_editor_internal.h create mode 100644 cpp/vendor/imgui-node-editor/imgui_node_editor_internal.inl create mode 100644 cpp/vendor/imgui-node-editor/misc/cmake-modules/FindScopeGuard.cmake create mode 100644 cpp/vendor/imgui-node-editor/misc/cmake-modules/Findgl3w.cmake create mode 100644 cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui.cmake create mode 100644 cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui_node_editor.cmake create mode 100644 cpp/vendor/imgui-node-editor/misc/cmake-modules/Findstb_image.cmake create mode 100644 cpp/vendor/imgui-node-editor/misc/crude_json.natvis create mode 100644 cpp/vendor/imgui-node-editor/misc/imgui_node_editor.natvis diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index a9bae6da..a1a74a89 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -43,6 +43,17 @@ if(TRACY_ENABLE) target_compile_definitions(tracy PUBLIC TRACY_ENABLE) endif() +# --- Vendor: imgui-node-editor --- +set(NODE_EDITOR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui-node-editor) +add_library(imgui_node_editor STATIC + ${NODE_EDITOR_DIR}/imgui_node_editor.cpp + ${NODE_EDITOR_DIR}/imgui_node_editor_api.cpp + ${NODE_EDITOR_DIR}/imgui_canvas.cpp + ${NODE_EDITOR_DIR}/crude_json.cpp +) +target_include_directories(imgui_node_editor PUBLIC ${NODE_EDITOR_DIR}) +target_link_libraries(imgui_node_editor PUBLIC imgui) + # --- Platform dependencies --- if(CMAKE_SYSTEM_NAME STREQUAL "Windows") # Cross-compile: use vendored or system GLFW, link opengl32/gdi32 diff --git a/cpp/apps/shaders_lab/CMakeLists.txt b/cpp/apps/shaders_lab/CMakeLists.txt index af898697..02716e20 100644 --- a/cpp/apps/shaders_lab/CMakeLists.txt +++ b/cpp/apps/shaders_lab/CMakeLists.txt @@ -11,11 +11,13 @@ add_imgui_app(shaders_lab ${CMAKE_SOURCE_DIR}/functions/gfx/dag_compile.cpp ${CMAKE_SOURCE_DIR}/functions/gfx/dag_uniforms.cpp ${CMAKE_SOURCE_DIR}/functions/gfx/dag_panel.cpp + ${CMAKE_SOURCE_DIR}/functions/gfx/dag_node_editor.cpp ${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp ) target_include_directories(shaders_lab PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ) +target_link_libraries(shaders_lab PRIVATE imgui_node_editor) if(WIN32) # GUI app: sin consola al lanzar (subsystem:windows / -mwindows) diff --git a/cpp/apps/shaders_lab/main.cpp b/cpp/apps/shaders_lab/main.cpp index bb2cafa8..7b9e6227 100644 --- a/cpp/apps/shaders_lab/main.cpp +++ b/cpp/apps/shaders_lab/main.cpp @@ -9,6 +9,7 @@ #include "gfx/dag_compile.h" #include "gfx/dag_uniforms.h" #include "gfx/dag_panel.h" +#include "gfx/dag_node_editor.h" #include "core/fps_overlay.h" #include "seed_shaders.h" @@ -142,7 +143,7 @@ static void render() { // --- DAG Pipeline window --- if (ImGui::Begin("DAG Pipeline")) { - if (fn::gfx::dag_panel(g_pipeline)) { + if (fn::gfx::dag_node_editor(g_pipeline)) { g_dag_dirty = true; } draw_err(g_dag_err, g_dag_err_line); @@ -205,5 +206,6 @@ int main() { int rc = fn::run_app(cfg, render); fn::gfx::canvas_destroy(g_canvas_code); fn::gfx::canvas_destroy(g_canvas_dag); + fn::gfx::dag_node_editor_destroy(); return rc; } diff --git a/cpp/functions/gfx/dag_catalog.cpp b/cpp/functions/gfx/dag_catalog.cpp index 65685d2d..dafd507d 100644 --- a/cpp/functions/gfx/dag_catalog.cpp +++ b/cpp/functions/gfx/dag_catalog.cpp @@ -14,6 +14,7 @@ static const std::vector& build_catalog() { n.label = "solid"; n.desc = "color constante"; n.kind = DagKind::Gen; + n.num_inputs = 0; n.param_names = {"r", "g", "b", ""}; n.param_defaults = {0.35f, 0.25f, 0.55f, 0.0f}; n.controls = { @@ -34,6 +35,7 @@ static const std::vector& build_catalog() { n.label = "gradient"; n.desc = "gradiente direccional"; n.kind = DagKind::Gen; + n.num_inputs = 0; n.param_names = {"angle", "hue", "", ""}; n.param_defaults = {0.8f, 0.5f, 0.0f, 0.0f}; n.controls = { @@ -58,6 +60,7 @@ static const std::vector& build_catalog() { n.label = "plasma"; n.desc = "onda trigonometrica"; n.kind = DagKind::Gen; + n.num_inputs = 0; n.param_names = {"speed", "scale", "", ""}; n.param_defaults = {1.0f, 2.0f, 0.0f, 0.0f}; n.controls = { @@ -73,13 +76,15 @@ static const std::vector& build_catalog() { v.push_back(std::move(n)); } - // ── Gen: circle ─────────────────────────────────────────────── + // ── Op: circle ──────────────────────────────────────────────── + // Reclassified as Op (num_inputs=1): composites circle over input 'a' { DagNodeDef n; n.name = "circle"; n.label = "circle"; - n.desc = "sdf de circulo"; - n.kind = DagKind::Gen; + n.desc = "sdf de circulo (composita sobre input)"; + n.kind = DagKind::Op; + n.num_inputs = 1; n.param_names = {"cx", "cy", "radius", "soft"}; n.param_defaults = {0.0f, 0.0f, 0.35f, 0.01f}; n.controls = { @@ -94,7 +99,7 @@ static const std::vector& build_catalog() { " vec2 pos = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n" " float d = length(pos) - p.z;\n" " float fill = smoothstep(p.w, -p.w, d);\n" - " return mix(c, vec4(1.0), fill);"; + " return mix(a, vec4(1.0), fill);"; }; v.push_back(std::move(n)); } @@ -106,11 +111,12 @@ static const std::vector& build_catalog() { n.label = "invert"; n.desc = "1 - rgb"; n.kind = DagKind::Op; + n.num_inputs = 1; n.param_names = {"", "", "", ""}; n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f}; n.controls = {}; n.body_glsl = [](int /*idx*/) -> std::string { - return " return vec4(1.0 - c.rgb, c.a);"; + return " return vec4(1.0 - a.rgb, a.a);"; }; v.push_back(std::move(n)); } @@ -122,6 +128,7 @@ static const std::vector& build_catalog() { n.label = "gamma"; n.desc = "pow(rgb, gamma)"; n.kind = DagKind::Op; + n.num_inputs = 1; n.param_names = {"gamma", "", "", ""}; n.param_defaults = {1.0f, 0.0f, 0.0f, 0.0f}; n.controls = { @@ -130,7 +137,7 @@ static const std::vector& build_catalog() { n.body_glsl = [](int idx) -> std::string { std::string i = std::to_string(idx); return " vec4 p = u_params[" + i + "];\n" - " return vec4(pow(c.rgb, vec3(1.0 / max(p.x, 0.001))), c.a);"; + " return vec4(pow(a.rgb, vec3(1.0 / max(p.x, 0.001))), a.a);"; }; v.push_back(std::move(n)); } @@ -142,6 +149,7 @@ static const std::vector& build_catalog() { n.label = "hue shift"; n.desc = "rotar matiz"; n.kind = DagKind::Op; + n.num_inputs = 1; n.param_names = {"h", "", "", ""}; n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f}; n.controls = { @@ -150,14 +158,14 @@ static const std::vector& build_catalog() { n.body_glsl = [](int idx) -> std::string { std::string i = std::to_string(idx); return " vec4 p = u_params[" + i + "];\n" - " float a = 6.28318 * p.x;\n" - " float ca = cos(a), sa = sin(a);\n" + " float ang = 6.28318 * p.x;\n" + " float ca = cos(ang), sa = sin(ang);\n" " mat3 hueMat = mat3(\n" " vec3(0.299 + 0.701 * ca + 0.168 * sa, 0.587 - 0.587 * ca + 0.330 * sa, 0.114 - 0.114 * ca - 0.497 * sa),\n" " vec3(0.299 - 0.299 * ca - 0.328 * sa, 0.587 + 0.413 * ca + 0.035 * sa, 0.114 - 0.114 * ca + 0.292 * sa),\n" " vec3(0.299 - 0.300 * ca + 1.250 * sa, 0.587 - 0.588 * ca - 1.050 * sa, 0.114 + 0.886 * ca - 0.203 * sa)\n" " );\n" - " return vec4(clamp(hueMat * c.rgb, 0.0, 1.0), c.a);"; + " return vec4(clamp(hueMat * a.rgb, 0.0, 1.0), a.a);"; }; v.push_back(std::move(n)); } @@ -169,6 +177,7 @@ static const std::vector& build_catalog() { n.label = "mix"; n.desc = "interpolacion mix(a, b, t)"; n.kind = DagKind::Blend; + n.num_inputs = 2; n.param_names = {"t", "", "", ""}; n.param_defaults = {0.5f, 0.0f, 0.0f, 0.0f}; n.controls = { @@ -189,6 +198,7 @@ static const std::vector& build_catalog() { n.label = "multiply"; n.desc = "a * b"; n.kind = DagKind::Blend; + n.num_inputs = 2; n.param_names = {"", "", "", ""}; n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f}; n.controls = {}; @@ -205,6 +215,7 @@ static const std::vector& build_catalog() { n.label = "screen"; n.desc = "1 - (1-a)(1-b)"; n.kind = DagKind::Blend; + n.num_inputs = 2; n.param_names = {"", "", "", ""}; n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f}; n.controls = {}; diff --git a/cpp/functions/gfx/dag_compile.cpp b/cpp/functions/gfx/dag_compile.cpp index 8f29d329..4e8a64a2 100644 --- a/cpp/functions/gfx/dag_compile.cpp +++ b/cpp/functions/gfx/dag_compile.cpp @@ -22,57 +22,64 @@ std::string compile_dag_to_glsl(const std::vector& pipeline) { return out.str(); } + // Emit per-node functions with signatures based on num_inputs for (int i = 0; i < n; ++i) { const DagStep& step = pipeline[static_cast(i)]; const DagNodeDef* def = dag_find(step.name); if (!def) continue; - if (def->kind == DagKind::Blend) { - out << "vec4 node_" << i << "(vec4 a, vec4 b, vec2 uv) {\n"; - } else { - out << "vec4 node_" << i << "(vec4 c, vec2 uv) {\n"; - } + int ni = def->num_inputs; + out << "vec4 node_" << i << "("; + if (ni >= 1) out << "vec4 a"; + if (ni >= 2) out << ", vec4 b"; + if (ni >= 3) out << ", vec4 c"; + if (ni >= 4) out << ", vec4 d"; + if (ni > 0) out << ", "; + out << "vec2 uv) {\n"; out << def->body_glsl(i) << "\n"; out << "}\n\n"; } out << "void main() {\n"; out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n"; - out << " vec4 c = vec4(0.04, 0.04, 0.06, 1.0);\n"; for (int i = 0; i < n; ++i) { const DagStep& step = pipeline[static_cast(i)]; const DagNodeDef* def = dag_find(step.name); if (!def) { - out << " vec4 out_" << i << " = (i > 0 ? out_" << (i-1) << " : c);\n"; + if (i == 0) { + out << " vec4 out_" << i << " = vec4(0.0, 0.0, 0.0, 1.0);\n"; + } else { + out << " vec4 out_" << i << " = out_" << (i - 1) << ";\n"; + } continue; } - std::string prev = (i == 0) ? "vec4(0.0, 0.0, 0.0, 1.0)" : "out_" + std::to_string(i - 1); + int ni = def->num_inputs; - if (def->kind == DagKind::Blend) { - int src_idx = -1; - if (!step.source_id.empty()) { + // Resolve each input slot + // For slot k: look for source_ids[k] in pipeline[0..i-1]; fallback = prev output + auto resolve = [&](int k) -> std::string { + const std::string& sid = step.source_ids[static_cast(k)]; + if (!sid.empty()) { for (int j = 0; j < i; ++j) { - if (pipeline[static_cast(j)].id == step.source_id) { - src_idx = j; - break; + if (pipeline[static_cast(j)].id == sid) { + return "out_" + std::to_string(j); } } } - if (src_idx < 0 || src_idx >= i) { - src_idx = std::max(0, i - 2); - } - std::string src; - if (i == 0) { - src = prev; - } else { - src = "out_" + std::to_string(std::min(src_idx, i - 1)); - } - out << " vec4 out_" << i << " = node_" << i << "(" << prev << ", " << src << ", uv);\n"; - } else { - out << " vec4 out_" << i << " = node_" << i << "(" << prev << ", uv);\n"; + // fallback + if (i == 0) return "vec4(0.0, 0.0, 0.0, 1.0)"; + return "out_" + std::to_string(i - 1); + }; + + out << " vec4 out_" << i << " = node_" << i << "("; + for (int k = 0; k < ni; ++k) { + if (k > 0) out << ", "; + out << resolve(k); } + if (ni > 0) out << ", "; + out << "uv);\n"; } out << " fragColor = out_" << (n - 1) << ";\n"; diff --git a/cpp/functions/gfx/dag_node_editor.cpp b/cpp/functions/gfx/dag_node_editor.cpp new file mode 100644 index 00000000..cf5a09ce --- /dev/null +++ b/cpp/functions/gfx/dag_node_editor.cpp @@ -0,0 +1,414 @@ +#include "gfx/dag_node_editor.h" +#include "gfx/dag_catalog.h" +#include "imgui.h" +#include "imgui_node_editor.h" +#include +#include +#include +#include +#include +#include + +namespace ed = ax::NodeEditor; + +namespace fn::gfx { + +static constexpr int MAX_NODES = 16; + +static ed::EditorContext* s_ctx = nullptr; +static uint32_t s_next_uid = 1; + +// ── ID encoding ────────────────────────────────────────────────────────────── +// node id = editor_uid +// output pin id = (editor_uid << 8) | 0 +// input pin id = (editor_uid << 8) | (slot + 1) slot 0..3 +// link id = (from_node_uid << 20) | (to_node_uid << 8) | slot + +static uintptr_t node_id(uint32_t uid) { + return static_cast(uid); +} +static uintptr_t output_pin_id(uint32_t uid) { + return (static_cast(uid) << 8) | 0u; +} +static uintptr_t input_pin_id(uint32_t uid, int slot) { + return (static_cast(uid) << 8) | static_cast(slot + 1); +} +static uintptr_t link_id(uint32_t from_uid, uint32_t to_uid, int slot) { + return (static_cast(from_uid) << 20) + | (static_cast(to_uid) << 8) + | static_cast(slot); +} + +// Decode pin id back +static bool is_output_pin(uintptr_t id) { return (id & 0xFF) == 0; } +static uint32_t uid_from_pin(uintptr_t id) { return static_cast(id >> 8); } +static int slot_from_input_pin(uintptr_t id) { return static_cast(id & 0xFF) - 1; } + +static int find_by_uid(const std::vector& p, uint32_t uid) { + for (int i = 0; i < static_cast(p.size()); ++i) { + if (p[static_cast(i)].editor_uid == uid) return i; + } + return -1; +} +static int find_by_id(const std::vector& p, const std::string& id) { + for (int i = 0; i < static_cast(p.size()); ++i) { + if (p[static_cast(i)].id == id) return i; + } + return -1; +} + +static ImVec4 kind_color(DagKind kind) { + switch (kind) { + case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f); + case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f); + case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f); + } + return ImVec4(1, 1, 1, 1); +} + +// Topological sort using Kahn's algorithm. +// Returns false if a cycle is detected (should not happen — BeginCreate rejects cycles). +static bool topo_sort(std::vector& pipeline) { + int n = static_cast(pipeline.size()); + if (n == 0) return true; + + // Build adjacency: edge from src -> dst if dst.source_ids[k] == src.id + std::unordered_map id_to_idx; + for (int i = 0; i < n; ++i) id_to_idx[pipeline[static_cast(i)].id] = i; + + std::vector in_degree(static_cast(n), 0); + std::vector> adj(static_cast(n)); + + for (int i = 0; i < n; ++i) { + const DagStep& s = pipeline[static_cast(i)]; + const DagNodeDef* def = dag_find(s.name); + int ni = def ? def->num_inputs : 0; + for (int k = 0; k < ni; ++k) { + const std::string& sid = s.source_ids[static_cast(k)]; + if (sid.empty()) continue; + auto it = id_to_idx.find(sid); + if (it == id_to_idx.end()) continue; + int src = it->second; + adj[static_cast(src)].push_back(i); + in_degree[static_cast(i)]++; + } + } + + std::queue q; + for (int i = 0; i < n; ++i) { + if (in_degree[static_cast(i)] == 0) q.push(i); + } + + std::vector order; + order.reserve(static_cast(n)); + while (!q.empty()) { + int u = q.front(); q.pop(); + order.push_back(u); + for (int v : adj[static_cast(u)]) { + if (--in_degree[static_cast(v)] == 0) q.push(v); + } + } + + if (static_cast(order.size()) != n) return false; // cycle + + std::vector sorted; + sorted.reserve(static_cast(n)); + for (int idx : order) sorted.push_back(pipeline[static_cast(idx)]); + pipeline = std::move(sorted); + return true; +} + +// Add node popup toolbar — rendered OUTSIDE ed::Begin +static bool draw_add_toolbar(std::vector& pipeline) { + bool changed = false; + int sz = static_cast(pipeline.size()); + if (ImGui::Button("+ Add Node")) { + ImGui::OpenPopup("ne_add_popup"); + } + ImGui::SameLine(); + ImGui::Text("%d/%d", sz, MAX_NODES); + ImGui::SameLine(); + if (ImGui::Button("Clear") && !pipeline.empty()) { + ImGui::OpenPopup("ne_clear_confirm"); + } + ImGui::SameLine(); + if (ImGui::Button("Fit")) { + if (s_ctx) { + ed::SetCurrentEditor(s_ctx); + ed::NavigateToContent(0.0f); + ed::SetCurrentEditor(nullptr); + } + } + + if (ImGui::BeginPopupModal("ne_clear_confirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Vaciar el pipeline?"); + if (ImGui::Button("Si")) { + pipeline.clear(); + changed = true; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::BeginPopup("ne_add_popup")) { + const char* kind_names[] = { "Gen", "Op", "Blend" }; + DagKind kinds[] = { DagKind::Gen, DagKind::Op, DagKind::Blend }; + for (int k = 0; k < 3; ++k) { + if (ImGui::BeginMenu(kind_names[k])) { + for (const auto& def : dag_catalog()) { + if (def.kind != kinds[k]) continue; + if (ImGui::MenuItem(def.label.c_str())) { + if (sz < MAX_NODES) { + DagStep step; + step.id = "n" + std::to_string(s_next_uid); + step.name = def.name; + step.params = def.param_defaults; + step.editor_uid = s_next_uid++; + // stagger position so nodes don't stack + step.editor_pos_x = 50.0f + static_cast(sz) * 220.0f; + step.editor_pos_y = 100.0f; + pipeline.push_back(step); + changed = true; + } + } + } + ImGui::EndMenu(); + } + } + ImGui::EndPopup(); + } + return changed; +} + +bool dag_node_editor(std::vector& pipeline) { + bool changed = false; + + // Ensure UIDs are assigned (e.g. for nodes created before this editor was active) + for (auto& step : pipeline) { + if (step.editor_uid == 0) { + step.editor_uid = s_next_uid++; + } + } + + // Create context on first call + if (!s_ctx) { + ed::Config cfg; + cfg.SettingsFile = nullptr; // no disk persistence + s_ctx = ed::CreateEditor(&cfg); + } + + changed |= draw_add_toolbar(pipeline); + ImGui::Separator(); + + ed::SetCurrentEditor(s_ctx); + ed::Begin("dag_editor", ImVec2(0.0f, 0.0f)); + + // ── Draw nodes ─────────────────────────────────────────────────────────── + for (int i = 0; i < static_cast(pipeline.size()); ++i) { + DagStep& step = pipeline[static_cast(i)]; + const DagNodeDef* def = dag_find(step.name); + if (!def) continue; + + ImVec4 col = kind_color(def->kind); + + ed::BeginNode(ed::NodeId(node_id(step.editor_uid))); + + // Header + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(def->label.c_str()); + ImGui::PopStyleColor(); + ImGui::Dummy(ImVec2(0, 2)); + + // Input pins (left column) + controls (middle) + output pin (right) + // We use a simple layout: input pins vertically, then controls, then output pin + int ni = def->num_inputs; + + // Input pins + for (int k = 0; k < ni; ++k) { + ed::BeginPin(ed::PinId(input_pin_id(step.editor_uid, k)), ed::PinKind::Input); + char lbl[16]; + std::snprintf(lbl, sizeof(lbl), "-> in%d", k); + ImGui::TextUnformatted(lbl); + ed::EndPin(); + } + + if (ni == 0) { + // Gen: no inputs, push a small placeholder so layout is consistent + ImGui::Dummy(ImVec2(4, 4)); + } + + // Controls + ImGui::PushID(static_cast(step.editor_uid)); + for (size_t ci = 0; ci < def->controls.size(); ++ci) { + const DagControl& ctrl = def->controls[ci]; + ImGui::SetNextItemWidth(150.0f); + char uid_lbl[64]; + std::snprintf(uid_lbl, sizeof(uid_lbl), "%s##%u%zu", ctrl.label.c_str(), step.editor_uid, ci); + if (ctrl.kind == DagControl::Kind::Slider) { + int pidx = ctrl.param_idx[0]; + if (pidx >= 0 && pidx < 4) { + ImGui::SliderFloat(uid_lbl, &step.params[static_cast(pidx)], ctrl.min, ctrl.max); + } + } else if (ctrl.kind == DagControl::Kind::XY) { + int px = ctrl.param_idx[0], py = ctrl.param_idx[1]; + if (px >= 0 && px < 4 && py >= 0 && py < 4 && py == px + 1) { + ImGui::SliderFloat2(uid_lbl, &step.params[static_cast(px)], ctrl.min, ctrl.max); + } + } else if (ctrl.kind == DagControl::Kind::Color) { + int pr = ctrl.param_idx[0]; + if (pr >= 0 && pr + 2 < 4) { + ImGui::ColorEdit3(uid_lbl, &step.params[static_cast(pr)]); + } + } + } + ImGui::PopID(); + + // Output pin + ImGui::Dummy(ImVec2(0, 2)); + ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output); + ImGui::Text("out ->"); + ed::EndPin(); + + ed::EndNode(); + + // Set initial position if not yet placed (both zero = first time) + if (step.editor_pos_x == 0.0f && step.editor_pos_y == 0.0f) { + step.editor_pos_x = 50.0f + static_cast(i) * 220.0f; + step.editor_pos_y = 100.0f; + } + ed::SetNodePosition(ed::NodeId(node_id(step.editor_uid)), + ImVec2(step.editor_pos_x, step.editor_pos_y)); + } + + // ── Draw existing links ────────────────────────────────────────────────── + for (int i = 0; i < static_cast(pipeline.size()); ++i) { + const DagStep& step = pipeline[static_cast(i)]; + const DagNodeDef* def = dag_find(step.name); + if (!def) continue; + for (int k = 0; k < def->num_inputs; ++k) { + const std::string& sid = step.source_ids[static_cast(k)]; + if (sid.empty()) continue; + int src_idx = find_by_id(pipeline, sid); + if (src_idx < 0) continue; + uint32_t src_uid = pipeline[static_cast(src_idx)].editor_uid; + ed::Link(ed::LinkId(link_id(src_uid, step.editor_uid, k)), + ed::PinId(output_pin_id(src_uid)), + ed::PinId(input_pin_id(step.editor_uid, k))); + } + } + + // ── Handle link creation ───────────────────────────────────────────────── + if (ed::BeginCreate()) { + ed::PinId start_pin, end_pin; + if (ed::QueryNewLink(&start_pin, &end_pin)) { + uintptr_t sp = start_pin.Get(); + uintptr_t ep = end_pin.Get(); + + // Normalise: start must be output, end must be input + if (!is_output_pin(sp) && is_output_pin(ep)) { + std::swap(sp, ep); + } + + bool valid = is_output_pin(sp) && !is_output_pin(ep); + if (valid) { + uint32_t from_uid = uid_from_pin(sp); + uint32_t to_uid = uid_from_pin(ep); + int slot = slot_from_input_pin(ep); + + int from_idx = find_by_uid(pipeline, from_uid); + int to_idx = find_by_uid(pipeline, to_uid); + + // Reject cycles: in topo-ordered list, from must come before to. + // We also reject self-loops. + if (from_uid == to_uid) { + ed::RejectNewItem(ImVec4(1, 0, 0, 1)); + valid = false; + } else if (from_idx >= to_idx) { + // Would create a back-edge; reject + ed::RejectNewItem(ImVec4(1, 0.5f, 0, 1)); + valid = false; + } + + if (valid && from_idx >= 0 && to_idx >= 0 && slot >= 0) { + if (ed::AcceptNewItem()) { + pipeline[static_cast(to_idx)].source_ids[static_cast(slot)] = + pipeline[static_cast(from_idx)].id; + changed = true; + } + } + } else { + ed::RejectNewItem(ImVec4(0.5f, 0.5f, 0.5f, 1)); + } + } + } + ed::EndCreate(); + + // ── Handle deletion ────────────────────────────────────────────────────── + if (ed::BeginDelete()) { + ed::LinkId del_lid; + while (ed::QueryDeletedLink(&del_lid)) { + if (ed::AcceptDeletedItem()) { + uintptr_t raw = del_lid.Get(); + uint32_t to_uid = static_cast((raw >> 8) & 0xFFF); + int slot = static_cast(raw & 0xFF); + int to_idx = find_by_uid(pipeline, to_uid); + if (to_idx >= 0 && slot < 4) { + pipeline[static_cast(to_idx)].source_ids[static_cast(slot)].clear(); + changed = true; + } + } + } + + ed::NodeId del_nid; + while (ed::QueryDeletedNode(&del_nid)) { + if (ed::AcceptDeletedItem()) { + uint32_t uid = static_cast(del_nid.Get()); + int idx = find_by_uid(pipeline, uid); + if (idx >= 0) { + const std::string& del_step_id = pipeline[static_cast(idx)].id; + // Clear any source_ids pointing to the deleted node + for (auto& step : pipeline) { + for (auto& sid : step.source_ids) { + if (sid == del_step_id) sid.clear(); + } + } + pipeline.erase(pipeline.begin() + idx); + changed = true; + } + } + } + } + ed::EndDelete(); + + // ── Save node positions back to steps ──────────────────────────────────── + for (auto& step : pipeline) { + auto pos = ed::GetNodePosition(ed::NodeId(node_id(step.editor_uid))); + step.editor_pos_x = pos.x; + step.editor_pos_y = pos.y; + } + + ed::End(); + ed::SetCurrentEditor(nullptr); + + // ── Topological sort after topology change ─────────────────────────────── + if (changed) { + if (!topo_sort(pipeline)) { + // Cycle detected — should not happen given BeginCreate validation + // but log a warning and leave order as-is + std::fprintf(stderr, "dag_node_editor: cycle detected, skipping topo sort\n"); + } + } + + return changed; +} + +void dag_node_editor_destroy() { + if (s_ctx) { + ed::DestroyEditor(s_ctx); + s_ctx = nullptr; + } +} + +} // namespace fn::gfx diff --git a/cpp/functions/gfx/dag_node_editor.h b/cpp/functions/gfx/dag_node_editor.h new file mode 100644 index 00000000..dbeaafe9 --- /dev/null +++ b/cpp/functions/gfx/dag_node_editor.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include "gfx/dag_types.h" + +namespace fn::gfx { + +// Renders the visual node editor (imgui-node-editor) inside the current ImGui::Begin. +// Modifies pipeline in-place: add/remove nodes, manage edges (source_ids). +// Returns true if TOPOLOGY changed (nodes or edges added/removed). +// Does not return true for position moves or slider changes. +// Call once per frame. Maintains its own editor context (static singleton). +bool dag_node_editor(std::vector& pipeline); + +// Release editor resources. Call on shutdown. +void dag_node_editor_destroy(); + +} // namespace fn::gfx diff --git a/cpp/functions/gfx/dag_node_editor.md b/cpp/functions/gfx/dag_node_editor.md new file mode 100644 index 00000000..20309ea0 --- /dev/null +++ b/cpp/functions/gfx/dag_node_editor.md @@ -0,0 +1,43 @@ +--- +name: dag_node_editor +kind: function +lang: cpp +domain: gfx +version: "1.0.0" +purity: impure +signature: "bool dag_node_editor(std::vector& pipeline)" +description: "Renderiza el node editor visual (imgui-node-editor) para el DAG de shaders. Modifica el pipeline in-place: añade/borra nodos, gestiona aristas (source_ids). Devuelve true si la topologia cambio." +tags: [dag, imgui, node-editor, shader, visual, pipeline, gfx] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: ["imgui_node_editor.h", "gfx/dag_catalog.h", "gfx/dag_types.h"] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/gfx/dag_node_editor.cpp" +params: + - name: pipeline + desc: "vector de DagStep que representa el pipeline actual; modificado in-place" +output: "true si la topologia cambio (nodos o aristas añadidos/quitados); false en caso contrario" +--- + +## Notas + +Sustituye a `dag_panel` como UI del pipeline. Usa `ax::NodeEditor` (thedmd/imgui-node-editor v0.9.4) con contexto singleton estático. + +Codificacion de IDs: +- Node id = `editor_uid` del step +- Output pin = `(uid << 8) | 0` +- Input pin = `(uid << 8) | (slot + 1)` +- Link id = `(from_uid << 20) | (to_uid << 8) | slot` + +Aplica topological sort (Kahn) tras cada cambio de topologia. Rechaza ciclos en `BeginCreate`. + +Multi-source: cada nodo declara `num_inputs` (0-4). Los slots de `source_ids[]` se mapean a los inputs del shader GLSL (`a`, `b`, `c`, `d`). + +## Dependencia + +Requiere `imgui_node_editor` static library linkeada (`cpp/vendor/imgui-node-editor`). diff --git a/cpp/functions/gfx/dag_panel.cpp b/cpp/functions/gfx/dag_panel.cpp index 589bf803..ad937ad4 100644 --- a/cpp/functions/gfx/dag_panel.cpp +++ b/cpp/functions/gfx/dag_panel.cpp @@ -76,7 +76,7 @@ bool dag_panel(std::vector& pipeline) { step.params = def.param_defaults; if (def.kind == DagKind::Blend && !pipeline.empty()) { int src = std::max(0, static_cast(pipeline.size()) - 2); - step.source_id = pipeline[static_cast(src)].id; + step.source_ids[1] = pipeline[static_cast(src)].id; } pipeline.push_back(step); changed = true; @@ -146,9 +146,9 @@ bool dag_panel(std::vector& pipeline) { } int current_src = std::max(0, i - 2); - if (!step.source_id.empty()) { + if (!step.source_ids[1].empty()) { for (int j = 0; j < i; ++j) { - if (pipeline[static_cast(j)].id == step.source_id) { + if (pipeline[static_cast(j)].id == step.source_ids[1]) { current_src = j; break; } @@ -161,7 +161,7 @@ bool dag_panel(std::vector& pipeline) { int sel = current_src; if (ImGui::Combo("Source", &sel, items.data(), static_cast(items.size()))) { if (sel >= 0 && sel < i) { - step.source_id = pipeline[static_cast(sel)].id; + step.source_ids[1] = pipeline[static_cast(sel)].id; changed = true; } } diff --git a/cpp/functions/gfx/dag_types.h b/cpp/functions/gfx/dag_types.h index 1851b126..cd2ae06d 100644 --- a/cpp/functions/gfx/dag_types.h +++ b/cpp/functions/gfx/dag_types.h @@ -23,6 +23,7 @@ struct DagNodeDef { std::string label; std::string desc; DagKind kind = DagKind::Gen; + int num_inputs = 0; // 0=Gen, 1=Op, 2=Blend, up to 4 std::array param_names{"", "", "", ""}; std::array param_defaults{0, 0, 0, 0}; std::vector controls; @@ -33,7 +34,10 @@ struct DagStep { std::string id; std::string name; std::array params{0, 0, 0, 0}; - std::string source_id; + std::array source_ids{"", "", "", ""}; // up to 4 inputs; "" = no connection + float editor_pos_x = 0.0f; + float editor_pos_y = 0.0f; + uint32_t editor_uid = 0; // monotonic counter, used as node editor ID }; } // namespace fn::gfx diff --git a/cpp/vendor/imgui-node-editor/.github/workflows/build.yml b/cpp/vendor/imgui-node-editor/.github/workflows/build.yml new file mode 100644 index 00000000..6c2ebc2e --- /dev/null +++ b/cpp/vendor/imgui-node-editor/.github/workflows/build.yml @@ -0,0 +1,61 @@ +name: build + +on: + push: + pull_request: + workflow_run: + # Use a workflow as a trigger of scheduled builds. Forked repositories can disable scheduled builds by disabling + # "scheduled" workflow, while maintaining ability to perform local CI builds. + workflows: + - scheduled + branches: + - master + - develop + types: + - requested + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + Windows: + runs-on: windows-2019 + env: + VS_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\ + MSBUILD_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\ + + steps: + - uses: actions/checkout@v2 + - name: Configure CMake + run: cmake -S examples -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + macOS: + runs-on: macos-latest + + steps: + - name: Install Dependencies + run: | + brew install glfw3 + - uses: actions/checkout@v2 + - name: Configure CMake + run: cmake -S examples -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + Linux: + runs-on: ubuntu-latest + + steps: + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y libglfw3-dev + - uses: actions/checkout@v2 + - name: Configure CMake + run: cmake -S examples -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + diff --git a/cpp/vendor/imgui-node-editor/.gitignore b/cpp/vendor/imgui-node-editor/.gitignore new file mode 100644 index 00000000..996f4687 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/.gitignore @@ -0,0 +1,11 @@ +.vs +.vscode +.build* +.DS_Store +bin +[Bb]uild +*.VC.db +*.VC.opendb +*.user +*.ini +*.json diff --git a/cpp/vendor/imgui-node-editor/LICENSE b/cpp/vendor/imgui-node-editor/LICENSE new file mode 100644 index 00000000..ae0db1ae --- /dev/null +++ b/cpp/vendor/imgui-node-editor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Michał Cichoń + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cpp/vendor/imgui-node-editor/crude_json.cpp b/cpp/vendor/imgui-node-editor/crude_json.cpp new file mode 100644 index 00000000..078427a2 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/crude_json.cpp @@ -0,0 +1,890 @@ +// Crude implementation of JSON value object and parser. +// +// VERSION 0.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +# include "crude_json.h" +# include +# include +# include +# include +# include +# include +# if CRUDE_JSON_IO +# include +# include +# endif + +namespace crude_json { + +value::value(value&& other) + : m_Type(other.m_Type) +{ + switch (m_Type) + { + case type_t::object: construct(m_Storage, std::move( *object_ptr(other.m_Storage))); break; + case type_t::array: construct(m_Storage, std::move( *array_ptr(other.m_Storage))); break; + case type_t::string: construct(m_Storage, std::move( *string_ptr(other.m_Storage))); break; + case type_t::boolean: construct(m_Storage, std::move(*boolean_ptr(other.m_Storage))); break; + case type_t::number: construct(m_Storage, std::move( *number_ptr(other.m_Storage))); break; + default: break; + } + destruct(other.m_Storage, other.m_Type); + other.m_Type = type_t::null; +} + +value::value(const value& other) + : m_Type(other.m_Type) +{ + switch (m_Type) + { + case type_t::object: construct(m_Storage, *object_ptr(other.m_Storage)); break; + case type_t::array: construct(m_Storage, *array_ptr(other.m_Storage)); break; + case type_t::string: construct(m_Storage, *string_ptr(other.m_Storage)); break; + case type_t::boolean: construct(m_Storage, *boolean_ptr(other.m_Storage)); break; + case type_t::number: construct(m_Storage, *number_ptr(other.m_Storage)); break; + default: break; + } +} + +value& value::operator[](size_t index) +{ + if (is_null()) + m_Type = construct(m_Storage, type_t::array); + + if (is_array()) + { + auto& v = *array_ptr(m_Storage); + if (index >= v.size()) + v.insert(v.end(), index - v.size() + 1, value()); + + return v[index]; + } + + CRUDE_ASSERT(false && "operator[] on unsupported type"); + std::terminate(); +} + +const value& value::operator[](size_t index) const +{ + if (is_array()) + return (*array_ptr(m_Storage))[index]; + + CRUDE_ASSERT(false && "operator[] on unsupported type"); + std::terminate(); +} + +value& value::operator[](const string& key) +{ + if (is_null()) + m_Type = construct(m_Storage, type_t::object); + + if (is_object()) + return (*object_ptr(m_Storage))[key]; + + CRUDE_ASSERT(false && "operator[] on unsupported type"); + std::terminate(); +} + +const value& value::operator[](const string& key) const +{ + if (is_object()) + { + auto& o = *object_ptr(m_Storage); + auto it = o.find(key); + CRUDE_ASSERT(it != o.end()); + return it->second; + } + + CRUDE_ASSERT(false && "operator[] on unsupported type"); + std::terminate(); +} + +bool value::contains(const string& key) const +{ + if (is_object()) + { + auto& o = *object_ptr(m_Storage); + auto it = o.find(key); + return it != o.end(); + } + + return false; +} + +void value::push_back(const value& value) +{ + if (is_null()) + m_Type = construct(m_Storage, type_t::array); + + if (is_array()) + { + auto& v = *array_ptr(m_Storage); + v.push_back(value); + } + else + { + CRUDE_ASSERT(false && "operator[] on unsupported type"); + std::terminate(); + } +} + +void value::push_back(value&& value) +{ + if (is_null()) + m_Type = construct(m_Storage, type_t::array); + + if (is_array()) + { + auto& v = *array_ptr(m_Storage); + v.push_back(std::move(value)); + } + else + { + CRUDE_ASSERT(false && "operator[] on unsupported type"); + std::terminate(); + } +} + +size_t value::erase(const string& key) +{ + if (!is_object()) + return 0; + + auto& o = *object_ptr(m_Storage); + auto it = o.find(key); + + if (it == o.end()) + return 0; + + o.erase(it); + + return 1; +} + +void value::swap(value& other) +{ + using std::swap; + + if (m_Type == other.m_Type) + { + switch (m_Type) + { + case type_t::object: swap(*object_ptr(m_Storage), *object_ptr(other.m_Storage)); break; + case type_t::array: swap(*array_ptr(m_Storage), *array_ptr(other.m_Storage)); break; + case type_t::string: swap(*string_ptr(m_Storage), *string_ptr(other.m_Storage)); break; + case type_t::boolean: swap(*boolean_ptr(m_Storage), *boolean_ptr(other.m_Storage)); break; + case type_t::number: swap(*number_ptr(m_Storage), *number_ptr(other.m_Storage)); break; + default: break; + } + } + else + { + value tmp(std::move(other)); + other.~value(); + new (&other) value(std::move(*this)); + this->~value(); + new (this) value(std::move(tmp)); + } +} + +string value::dump(const int indent, const char indent_char) const +{ + dump_context_t context(indent, indent_char); + + context.out.precision(std::numeric_limits::max_digits10 + 1); + context.out << std::defaultfloat; + + dump(context, 0); + return context.out.str(); +} + +void value::dump_context_t::write_indent(int level) +{ + if (indent <= 0 || level == 0) + return; + + out.fill(indent_char); + out.width(indent * level); + out << indent_char; + out.width(0); +} + +void value::dump_context_t::write_separator() +{ + if (indent < 0) + return; + + out.put(' '); +} + +void value::dump_context_t::write_newline() +{ + if (indent < 0) + return; + + out.put('\n'); +} + +void value::dump(dump_context_t& context, int level) const +{ + context.write_indent(level); + + switch (m_Type) + { + case type_t::null: + context.out << "null"; + break; + + case type_t::object: + context.out << '{'; + { + context.write_newline(); + bool first = true; + for (auto& entry : *object_ptr(m_Storage)) + { + if (!first) { context.out << ','; context.write_newline(); } else first = false; + context.write_indent(level + 1); + context.out << '\"' << entry.first << "\":"; + if (!entry.second.is_structured()) + { + context.write_separator(); + entry.second.dump(context, 0); + } + else + { + context.write_newline(); + entry.second.dump(context, level + 1); + } + } + if (!first) + context.write_newline(); + } + context.write_indent(level); + context.out << '}'; + break; + + case type_t::array: + context.out << '['; + { + context.write_newline(); + bool first = true; + for (auto& entry : *array_ptr(m_Storage)) + { + if (!first) { context.out << ','; context.write_newline(); } else first = false; + if (!entry.is_structured()) + { + context.write_indent(level + 1); + entry.dump(context, 0); + } + else + { + entry.dump(context, level + 1); + } + } + if (!first) + context.write_newline(); + } + context.write_indent(level); + context.out << ']'; + break; + + case type_t::string: + context.out << '\"'; + + if (string_ptr(m_Storage)->find_first_of("\"\\/\b\f\n\r") != string::npos || string_ptr(m_Storage)->find('\0') != string::npos) + { + for (auto c : *string_ptr(m_Storage)) + { + if (c == '\"') context.out << "\\\""; + else if (c == '\\') context.out << "\\\\"; + else if (c == '/') context.out << "\\/"; + else if (c == '\b') context.out << "\\b"; + else if (c == '\f') context.out << "\\f"; + else if (c == '\n') context.out << "\\n"; + else if (c == '\r') context.out << "\\r"; + else if (c == '\t') context.out << "\\t"; + else if (c == 0) context.out << "\\u0000"; + else context.out << c; + } + } + else + context.out << *string_ptr(m_Storage); + context.out << '\"'; + break; + + + case type_t::boolean: + if (*boolean_ptr(m_Storage)) + context.out << "true"; + else + context.out << "false"; + break; + + case type_t::number: + context.out << *number_ptr(m_Storage); + break; + + default: + break; + } +} + +struct value::parser +{ + parser(const char* begin, const char* end) + : m_Cursor(begin) + , m_End(end) + { + } + + value parse() + { + value v; + + // Switch to C locale to make strtod and strtol work as expected + auto previous_locale = std::setlocale(LC_NUMERIC, "C"); + + // Accept single value only when end of the stream is reached. + if (!accept_element(v) || !eof()) + v = value(type_t::discarded); + + if (previous_locale && strcmp(previous_locale, "C") != 0) + std::setlocale(LC_NUMERIC, previous_locale); + + return v; + } + +private: + struct cursor_state + { + cursor_state(parser* p) + : m_Owner(p) + , m_LastCursor(p->m_Cursor) + { + } + + void reset() + { + m_Owner->m_Cursor = m_LastCursor; + } + + bool operator()(bool accept) + { + if (!accept) + reset(); + else + m_LastCursor = m_Owner->m_Cursor; + return accept; + } + + private: + parser* m_Owner; + const char* m_LastCursor; + }; + + cursor_state state() + { + return cursor_state(this); + } + + bool accept_value(value& result) + { + return accept_object(result) + || accept_array(result) + || accept_string(result) + || accept_number(result) + || accept_boolean(result) + || accept_null(result); + } + + bool accept_object(value& result) + { + auto s = state(); + + object o; + if (s(accept('{') && accept_ws() && accept('}'))) + { + result = o; + return true; + } + else if (s(accept('{') && accept_members(o) && accept('}'))) + { + result = std::move(o); + return true; + } + + return false; + } + + bool accept_members(object& o) + { + if (!accept_member(o)) + return false; + + while (true) + { + auto s = state(); + if (!s(accept(',') && accept_member(o))) + break; + } + + return true; + } + + bool accept_member(object& o) + { + auto s = state(); + + value key; + value v; + if (s(accept_ws() && accept_string(key) && accept_ws() && accept(':') && accept_element(v))) + { + o.emplace(std::move(key.get()), std::move(v)); + return true; + } + + return false; + } + + bool accept_array(value& result) + { + auto s = state(); + + if (s(accept('[') && accept_ws() && accept(']'))) + { + result = array(); + return true; + } + + array a; + if (s(accept('[') && accept_elements(a) && accept(']'))) + { + result = std::move(a); + return true; + } + + return false; + } + + bool accept_elements(array& a) + { + value v; + if (!accept_element(v)) + return false; + + a.emplace_back(std::move(v)); + while (true) + { + auto s = state(); + v = nullptr; + if (!s(accept(',') && accept_element(v))) + break; + a.emplace_back(std::move(v)); + } + + return true; + } + + bool accept_element(value& result) + { + auto s = state(); + return s(accept_ws() && accept_value(result) && accept_ws()); + } + + bool accept_string(value& result) + { + auto s = state(); + + string v; + if (s(accept('\"') && accept_characters(v) && accept('\"'))) + { + result = std::move(v); + return true; + } + else + return false; + } + + bool accept_characters(string& result) + { + int c; + while (accept_character(c)) + { + CRUDE_ASSERT(c < 128); // #todo: convert characters > 127 to UTF-8 + result.push_back(static_cast(c)); + } + + return true; + } + + bool accept_character(int& c) + { + auto s = state(); + + if (accept('\\')) + { + return accept_escape(c); + } + else if (expect('\"')) + return false; + + // #todo: Handle UTF-8 sequences. + return s((c = peek()) >= 0) && advance(); + } + + bool accept_escape(int& c) + { + if (accept('\"')) { c = '\"'; return true; } + if (accept('\\')) { c = '\\'; return true; } + if (accept('/')) { c = '/'; return true; } + if (accept('b')) { c = '\b'; return true; } + if (accept('f')) { c = '\f'; return true; } + if (accept('n')) { c = '\n'; return true; } + if (accept('r')) { c = '\r'; return true; } + if (accept('t')) { c = '\t'; return true; } + + auto s = state(); + + string hex; + hex.reserve(4); + if (s(accept('u') && accept_hex(hex) && accept_hex(hex) && accept_hex(hex) && accept_hex(hex))) + { + char* end = nullptr; + auto v = std::strtol(hex.c_str(), &end, 16); + if (end != hex.c_str() + hex.size()) + return false; + + c = static_cast(v); + return true; + } + + return false; + } + + bool accept_hex(string& result) + { + if (accept_digit(result)) + return true; + + auto c = peek(); + if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + { + advance(); + result.push_back(static_cast(c)); + return true; + } + + return false; + } + + bool accept_number(value& result) + { + auto s = state(); + + string n; + if (s(accept_int(n) && accept_frac(n) && accept_exp(n))) + { + char* end = nullptr; + auto v = std::strtod(n.c_str(), &end); + if (end != n.c_str() + n.size()) + return false; + + if (v != 0 && !std::isnormal(v)) + return false; + + result = v; + return true; + } + + return false; + } + + bool accept_int(string& result) + { + auto s = state(); + + string part; + if (s(accept_onenine(part) && accept_digits(part))) + { + result += std::move(part); + return true; + } + + part.resize(0); + if (accept_digit(part)) + { + result += std::move(part); + return true; + } + + part.resize(0); + if (s(accept('-') && accept_onenine(part) && accept_digits(part))) + { + result += '-'; + result += std::move(part); + return true; + } + + part.resize(0); + if (s(accept('-') && accept_digit(part))) + { + result += '-'; + result += std::move(part); + return true; + } + + return false; + } + + bool accept_digits(string& result) + { + string part; + if (!accept_digit(part)) + return false; + + while (accept_digit(part)) + ; + + result += std::move(part); + + return true; + } + + bool accept_digit(string& result) + { + if (accept('0')) + { + result.push_back('0'); + return true; + } + else if (accept_onenine(result)) + return true; + + return false; + } + + bool accept_onenine(string& result) + { + auto c = peek(); + if (c >= '1' && c <= '9') + { + result.push_back(static_cast(c)); + return advance(); + } + + return false; + } + + bool accept_frac(string& result) + { + auto s = state(); + + string part; + if (s(accept('.') && accept_digits(part))) + { + result += '.'; + result += std::move(part); + } + + return true; + } + + bool accept_exp(string& result) + { + auto s = state(); + + string part; + if (s(accept('e') && accept_sign(part) && accept_digits(part))) + { + result += 'e'; + result += std::move(part); + return true; + } + part.resize(0); + if (s(accept('E') && accept_sign(part) && accept_digits(part))) + { + result += 'E'; + result += std::move(part); + } + + return true; + } + + bool accept_sign(string& result) + { + if (accept('+')) + result.push_back('+'); + else if (accept('-')) + result.push_back('-'); + + return true; + } + + bool accept_ws() + { + while (expect('\x09') || expect('\x0A') || expect('\x0D') || expect('\x20')) + advance(); + return true; + } + + bool accept_boolean(value& result) + { + if (accept("true")) + { + result = true; + return true; + } + else if (accept("false")) + { + result = false; + return true; + } + + return false; + } + + bool accept_null(value& result) + { + if (accept("null")) + { + result = nullptr; + return true; + } + + return false; + } + + bool accept(char c) + { + if (expect(c)) + return advance(); + else + return false; + } + + bool accept(const char* str) + { + auto last = m_Cursor; + + while (*str) + { + if (eof() || *str != *m_Cursor) + { + m_Cursor = last; + return false; + } + + advance(); + ++str; + } + + return true; + } + + int peek() const + { + if (!eof()) + return *m_Cursor; + else + return -1; + } + + bool expect(char c) + { + return peek() == c; + } + + bool advance(int count = 1) + { + if (m_Cursor + count > m_End) + { + m_Cursor = m_End; + return false; + } + + m_Cursor += count; + + return true; + } + + bool eof() const + { + return m_Cursor == m_End; + } + + const char* m_Cursor; + const char* m_End; +}; + +value value::parse(const string& data) +{ + auto p = parser(data.c_str(), data.c_str() + data.size()); + + auto v = p.parse(); + + return v; +} + +# if CRUDE_JSON_IO +std::pair value::load(const string& path) +{ + // Modern C++, so beautiful... + std::unique_ptr file{nullptr, [](FILE* file) { if (file) fclose(file); }}; +# if defined(_MSC_VER) || (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__) + FILE* handle = nullptr; + if (fopen_s(&handle, path.c_str(), "rb") != 0) + return {value{}, false}; + file.reset(handle); +# else + file.reset(fopen(path.c_str(), "rb")); +# endif + + if (!file) + return {value{}, false}; + + fseek(file.get(), 0, SEEK_END); + auto size = static_cast(ftell(file.get())); + fseek(file.get(), 0, SEEK_SET); + + string data; + data.resize(size); + if (fread(const_cast(data.data()), size, 1, file.get()) != 1) + return {value{}, false}; + + return {parse(data), true}; +} + +bool value::save(const string& path, const int indent, const char indent_char) const +{ + // Modern C++, so beautiful... + std::unique_ptr file{nullptr, [](FILE* file) { if (file) fclose(file); }}; +# if defined(_MSC_VER) || (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__) + FILE* handle = nullptr; + if (fopen_s(&handle, path.c_str(), "wb") != 0) + return false; + file.reset(handle); +# else + file.reset(fopen(path.c_str(), "wb")); +# endif + + if (!file) + return false; + + auto data = dump(indent, indent_char); + + if (fwrite(data.data(), data.size(), 1, file.get()) != 1) + return false; + + return true; +} + +# endif + +} // namespace crude_json diff --git a/cpp/vendor/imgui-node-editor/crude_json.h b/cpp/vendor/imgui-node-editor/crude_json.h new file mode 100644 index 00000000..f1eff29e --- /dev/null +++ b/cpp/vendor/imgui-node-editor/crude_json.h @@ -0,0 +1,250 @@ +// Crude implementation of JSON value object and parser. +// +// VERSION 0.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +# ifndef __CRUDE_JSON_H__ +# define __CRUDE_JSON_H__ +# pragma once + +# include +# include +# include +# include +# include +# include +# include + +# ifndef CRUDE_ASSERT +# include +# define CRUDE_ASSERT(expr) assert(expr) +# endif + +# ifndef CRUDE_JSON_IO +# define CRUDE_JSON_IO 1 +# endif + +namespace crude_json { + +struct value; + +using string = std::string; +using object = std::map; +using array = std::vector; +using number = double; +using boolean = bool; +using null = std::nullptr_t; + +enum class type_t +{ + null, + object, + array, + string, + boolean, + number, + discarded +}; + +struct value +{ + value(type_t type = type_t::null): m_Type(construct(m_Storage, type)) {} + value(value&& other); + value(const value& other); + + value( null) : m_Type(construct(m_Storage, null())) {} + value( object&& v): m_Type(construct(m_Storage, std::move(v))) {} + value(const object& v): m_Type(construct(m_Storage, v)) {} + value( array&& v): m_Type(construct(m_Storage, std::move(v))) {} + value(const array& v): m_Type(construct(m_Storage, v)) {} + value( string&& v): m_Type(construct(m_Storage, std::move(v))) {} + value(const string& v): m_Type(construct(m_Storage, v)) {} + value(const char* v): m_Type(construct(m_Storage, v)) {} + value( boolean v): m_Type(construct(m_Storage, v)) {} + value( number v): m_Type(construct(m_Storage, v)) {} + ~value() { destruct(m_Storage, m_Type); } + + value& operator=(value&& other) { if (this != &other) { value(std::move(other)).swap(*this); } return *this; } + value& operator=(const value& other) { if (this != &other) { value( other).swap(*this); } return *this; } + + value& operator=( null) { auto other = value( ); swap(other); return *this; } + value& operator=( object&& v) { auto other = value(std::move(v)); swap(other); return *this; } + value& operator=(const object& v) { auto other = value( v); swap(other); return *this; } + value& operator=( array&& v) { auto other = value(std::move(v)); swap(other); return *this; } + value& operator=(const array& v) { auto other = value( v); swap(other); return *this; } + value& operator=( string&& v) { auto other = value(std::move(v)); swap(other); return *this; } + value& operator=(const string& v) { auto other = value( v); swap(other); return *this; } + value& operator=(const char* v) { auto other = value( v); swap(other); return *this; } + value& operator=( boolean v) { auto other = value( v); swap(other); return *this; } + value& operator=( number v) { auto other = value( v); swap(other); return *this; } + + type_t type() const { return m_Type; } + + operator type_t() const { return m_Type; } + + value& operator[](size_t index); + const value& operator[](size_t index) const; + value& operator[](const string& key); + const value& operator[](const string& key) const; + + bool contains(const string& key) const; + + void push_back(const value& value); + void push_back(value&& value); + + size_t erase(const string& key); + + bool is_primitive() const { return is_string() || is_number() || is_boolean() || is_null(); } + bool is_structured() const { return is_object() || is_array(); } + bool is_null() const { return m_Type == type_t::null; } + bool is_object() const { return m_Type == type_t::object; } + bool is_array() const { return m_Type == type_t::array; } + bool is_string() const { return m_Type == type_t::string; } + bool is_boolean() const { return m_Type == type_t::boolean; } + bool is_number() const { return m_Type == type_t::number; } + bool is_discarded() const { return m_Type == type_t::discarded; } + + template const T& get() const; + template T& get(); + + template const T* get_ptr() const; + template T* get_ptr(); + + string dump(const int indent = -1, const char indent_char = ' ') const; + + void swap(value& other); + + inline friend void swap(value& lhs, value& rhs) { lhs.swap(rhs); } + + // Returns discarded value for invalid inputs. + static value parse(const string& data); + +# if CRUDE_JSON_IO + static std::pair load(const string& path); + bool save(const string& path, const int indent = -1, const char indent_char = ' ') const; +# endif + +private: + struct parser; + + // VS2015: std::max() is not constexpr yet. +# define CRUDE_MAX2(a, b) ((a) < (b) ? (b) : (a)) +# define CRUDE_MAX3(a, b, c) CRUDE_MAX2(CRUDE_MAX2(a, b), c) +# define CRUDE_MAX4(a, b, c, d) CRUDE_MAX2(CRUDE_MAX3(a, b, c), d) +# define CRUDE_MAX5(a, b, c, d, e) CRUDE_MAX2(CRUDE_MAX4(a, b, c, d), e) + enum + { + max_size = CRUDE_MAX5( sizeof(string), sizeof(object), sizeof(array), sizeof(number), sizeof(boolean)), + max_align = CRUDE_MAX5(alignof(string), alignof(object), alignof(array), alignof(number), alignof(boolean)) + }; +# undef CRUDE_MAX5 +# undef CRUDE_MAX4 +# undef CRUDE_MAX3 +# undef CRUDE_MAX2 + using storage_t = std::aligned_storage::type; + + static object* object_ptr( storage_t& storage) { return reinterpret_cast< object*>(&storage); } + static const object* object_ptr(const storage_t& storage) { return reinterpret_cast(&storage); } + static array* array_ptr( storage_t& storage) { return reinterpret_cast< array*>(&storage); } + static const array* array_ptr(const storage_t& storage) { return reinterpret_cast(&storage); } + static string* string_ptr( storage_t& storage) { return reinterpret_cast< string*>(&storage); } + static const string* string_ptr(const storage_t& storage) { return reinterpret_cast(&storage); } + static boolean* boolean_ptr( storage_t& storage) { return reinterpret_cast< boolean*>(&storage); } + static const boolean* boolean_ptr(const storage_t& storage) { return reinterpret_cast(&storage); } + static number* number_ptr( storage_t& storage) { return reinterpret_cast< number*>(&storage); } + static const number* number_ptr(const storage_t& storage) { return reinterpret_cast(&storage); } + + static type_t construct(storage_t& storage, type_t type) + { + switch (type) + { + case type_t::object: new (&storage) object(); break; + case type_t::array: new (&storage) array(); break; + case type_t::string: new (&storage) string(); break; + case type_t::boolean: new (&storage) boolean(); break; + case type_t::number: new (&storage) number(); break; + default: break; + } + + return type; + } + + static type_t construct(storage_t& storage, null) { (void)storage; return type_t::null; } + static type_t construct(storage_t& storage, object&& value) { new (&storage) object(std::forward(value)); return type_t::object; } + static type_t construct(storage_t& storage, const object& value) { new (&storage) object(value); return type_t::object; } + static type_t construct(storage_t& storage, array&& value) { new (&storage) array(std::forward(value)); return type_t::array; } + static type_t construct(storage_t& storage, const array& value) { new (&storage) array(value); return type_t::array; } + static type_t construct(storage_t& storage, string&& value) { new (&storage) string(std::forward(value)); return type_t::string; } + static type_t construct(storage_t& storage, const string& value) { new (&storage) string(value); return type_t::string; } + static type_t construct(storage_t& storage, const char* value) { new (&storage) string(value); return type_t::string; } + static type_t construct(storage_t& storage, boolean value) { new (&storage) boolean(value); return type_t::boolean; } + static type_t construct(storage_t& storage, number value) { new (&storage) number(value); return type_t::number; } + + static void destruct(storage_t& storage, type_t type) + { + switch (type) + { + case type_t::object: object_ptr(storage)->~object(); break; + case type_t::array: array_ptr(storage)->~array(); break; + case type_t::string: string_ptr(storage)->~string(); break; + default: break; + } + } + + struct dump_context_t + { + std::ostringstream out; + const int indent = -1; + const char indent_char = ' '; + + // VS2015: Aggregate initialization isn't a thing yet. + dump_context_t(const int indent, const char indent_char) + : indent(indent) + , indent_char(indent_char) + { + } + + void write_indent(int level); + void write_separator(); + void write_newline(); + }; + + void dump(dump_context_t& context, int level) const; + + storage_t m_Storage; + type_t m_Type; +}; + +template <> inline const object& value::get() const { CRUDE_ASSERT(m_Type == type_t::object); return *object_ptr(m_Storage); } +template <> inline const array& value::get() const { CRUDE_ASSERT(m_Type == type_t::array); return *array_ptr(m_Storage); } +template <> inline const string& value::get() const { CRUDE_ASSERT(m_Type == type_t::string); return *string_ptr(m_Storage); } +template <> inline const boolean& value::get() const { CRUDE_ASSERT(m_Type == type_t::boolean); return *boolean_ptr(m_Storage); } +template <> inline const number& value::get() const { CRUDE_ASSERT(m_Type == type_t::number); return *number_ptr(m_Storage); } + +template <> inline object& value::get() { CRUDE_ASSERT(m_Type == type_t::object); return *object_ptr(m_Storage); } +template <> inline array& value::get() { CRUDE_ASSERT(m_Type == type_t::array); return *array_ptr(m_Storage); } +template <> inline string& value::get() { CRUDE_ASSERT(m_Type == type_t::string); return *string_ptr(m_Storage); } +template <> inline boolean& value::get() { CRUDE_ASSERT(m_Type == type_t::boolean); return *boolean_ptr(m_Storage); } +template <> inline number& value::get() { CRUDE_ASSERT(m_Type == type_t::number); return *number_ptr(m_Storage); } + +template <> inline const object* value::get_ptr() const { if (m_Type == type_t::object) return object_ptr(m_Storage); else return nullptr; } +template <> inline const array* value::get_ptr() const { if (m_Type == type_t::array) return array_ptr(m_Storage); else return nullptr; } +template <> inline const string* value::get_ptr() const { if (m_Type == type_t::string) return string_ptr(m_Storage); else return nullptr; } +template <> inline const boolean* value::get_ptr() const { if (m_Type == type_t::boolean) return boolean_ptr(m_Storage); else return nullptr; } +template <> inline const number* value::get_ptr() const { if (m_Type == type_t::number) return number_ptr(m_Storage); else return nullptr; } + +template <> inline object* value::get_ptr() { if (m_Type == type_t::object) return object_ptr(m_Storage); else return nullptr; } +template <> inline array* value::get_ptr() { if (m_Type == type_t::array) return array_ptr(m_Storage); else return nullptr; } +template <> inline string* value::get_ptr() { if (m_Type == type_t::string) return string_ptr(m_Storage); else return nullptr; } +template <> inline boolean* value::get_ptr() { if (m_Type == type_t::boolean) return boolean_ptr(m_Storage); else return nullptr; } +template <> inline number* value::get_ptr() { if (m_Type == type_t::number) return number_ptr(m_Storage); else return nullptr; } + +} // namespace crude_json + +# endif // __CRUDE_JSON_H__ \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/docs/CHANGELOG.txt b/cpp/vendor/imgui-node-editor/docs/CHANGELOG.txt new file mode 100644 index 00000000..0e70996e --- /dev/null +++ b/cpp/vendor/imgui-node-editor/docs/CHANGELOG.txt @@ -0,0 +1,289 @@ +v0.9.4 (WIP): + + NEW: Editor: Add smooth zoom (#266) + + BUGFIX: Canvas: Remember index of first command buffer to not miss updating any used (#260) + + BUGFIX: Editor: Don't duplicated ImVec2/ImVec3 == != operators defined since ImGui r19002 (#268) + + BUGFIX: Examples: Use imgui_impl_opengl3_loader.h instead of gl3w (#264) + +v0.9.3 (2023-10-14): + + CHANGE: Canvas: Use ImDrawCallback_ImCanvas macro as draw callback sentinel (#256), thanks @nspitko + + BUGFIX: Canvas: Ensure SentinelDrawCallback cleanup (#255) + + BUGFIX: Editor: Don't call Reasume/Suspend on invisible canvas (#255) + +v0.9.2 (2023-09-01): + + NEW: Editor: Add offset of hover/select to style (thanks @MultiPain) + + NEW: Editor: Add IMGUI_NODE_EDITOR_API to support building editor as a shared library (#189) + + NEW: Canvas: Add IMGUIEX_CANVAS_API to support building canvas as a shared library (#189) + + CHANGE: Editor: Support ImGui r18836 after SetItemUsingMouseWheel removal (#218), thanks @ocornut + + CHANGE: Editor: Define IMGUI_DEFINE_MATH_OPERATORS before (#209), thanks @ocornut + + CHANGE: Examples: Define IMGUI_DEFINE_MATH_OPERATORS before (#209), thanks @ocornut + + CHANGE: Canvas: Don't use deprecated SetItemAllowOverlap (#250) + + CHANGE: Examples: Don't use deprecated SetItemAllowOverlap (#250) + + CHANGE: Editor: Define IMGUI_DEFINE_MATH_OPERATORS before (#209), thanks @ocornut + + CHANGE: Editor: Unary operator- for ImVec2 is defined by ImGui since r18955 (#248) + + BUGFIX: Editor: Correctly initialize 'width' for view resize code (thx @gnif) + + BUGFIX: Examples: Handle node deletion before links (#182) + Deleting node queue connected links for deletion. + + BUGFIX: Examples: Simplify and fix drawing of node header line (#180) + + BUGFIX: Editor: Cleanup tabs. + + BUGFIX: Editor: Use ImGuiKey directly with ImGui r18822 (#183) + + BUGFIX: Examples: Use ImGuiKey directly with ImGui r18822 (#183) + + BUGFIX: Examples: Use ImGuiKey_KeypadEnter with ImGui r18604 (#183) + + BUGFIX: Examples: Add missing include for std::intptr_t (#199) + + BUGFIX: Examples: Don't use empty string as identifier + + BUGFIX: Editor: Clean long to int implicit cast warning in crude_json + + BUGFIX: Canvas: Ensure canvas draw commands are separated from other ImGui draw commands (#205, #250) + + BUGFIX: Editor: Don't call Canvas.End() when Canvas.Begin() failed (#186), thanks @pthom, @TheZoc + + +v0.9.1 (2022-08-27): + + CHANGE: Remove unwanted extra frame height from node bottom + + CHANGE: Allow to specify if links of deleted node should also be automatically deleted + Now it is possible to delete only node without automatically serving links, + application can choose to do this operation by itself and for example + short circuit flow links ot do any other special operation. + + CHANGE: Canvas: Allow to overlap canvas widget + + CHANGE: Natvis: Move crude_json natvis to separate file + + CHANGE: Natvis: Show readable NodeId/PinId/LinkId + + CHANGE: Make Navigate action to honor duration + + CHANGE: Travis: Use Ubuntu Bionic (18.04) for CI, to get newer version of GLFW3 + + CHANGE: Editor: Make action button internally configurable + + CHANGE: Make Node Editor forward compatible with ImGui 1.80+ (#112) + We're keeping backward compatibility with pre 1.8x versions. + + CHANGE: Update internal copy ImGui to 1.84 (WIP) (3512f2c2c283ec86) (#107) + Internal copy has two PR's merged: + https://github.com/thedmd/imgui/tree/feature/layout - used in blueprints example only + https://github.com/thedmd/imgui/tree/feature/extra-keys - optional: used by Node Editor if present + + CHANGE: Use github actions instead of Travis and AppVeyor (#113) + + CHANGE: Delete operation on node/link will remove internal object (#173) + + CHANGE: Natvis: Add crude_json::value visualization + + NEW: All source components are now versioned + + NEW: Make view state independent of window resolution. + + NEW: Editor can now break links connected specified node or pin + New API: + int BreakLinks(NodeId nodeId); + int BreakLinks(PinId pinId); + + NEW: Editor can now tell if node or pin has any links attached + New API: + bool HasAnyLinks(NodeId nodeId); + bool HasAnyLinks(PinId pinId); + + NEW: Editor can be queried if particular node or link is selected + New API: + bool IsNodeSelected(NodeId nodeId); + bool IsLinkSelected(LinkId linkId); + + NEW: Editor now can return pins of the link + New API: + bool GetLinkPins(LinkId linkId, PinId* startPinId, PinId* endPinId); + + `startPinId` and `endPinId` may be null if caller is not interested + in particular id. + + NEW: Editor now return ids of hovered node/pin/link + New API: + NodeId GetHoveredNode(); + PinId GetHoveredPin(); + LinkId GetHoveredLink(); + + NEW: Add SetGroupSize() to explicitly set group size + New API: + void SetGroupSize(NodeId nodeId, const ImVec2& size); + + NEW: crude_json: Add save() and load() + + When CRUDE_JSON_IO == 1 value will have load() and save() + function implemented using stdio.h FILE. + + NEW: crude_json: Add erase() and get_ptr() + + NEW: Application overhaul + - Convert from function based to inheritable class + - Add ability to close app and change title from code + - Add ability to control main window flags (ex. show menubar) + - Save ImGui state to ini file + - Render using pre-multiplied alpha textures + - Add extra fonts to examples. + + NEW: Reintegrate Widgets example from @crolando (#77) + + NEW: User can now override button indices for various actions (#88) + New API in Config: + int DragButtonIndex; // Mouse button index drag action will react to (0-left, 1-right, 2-middle) + int SelectButtonIndex; // Mouse button index select action will react to (0-left, 1-right, 2-middle) + int NavigateButtonIndex; // Mouse button index navigate action will react to (0-left, 1-right, 2-middle) + int ContextMenuButtonIndex; // Mouse button index context menu action will react to (0-left, 1-right, 2-middle) + + NEW: Flow direction can now be picked per flow (#104) + New API: + enum class FlowDirection + { + Forward, + Backward + }; + + void Flow(LinkId linkId, FlowDirection direction = FlowDirection::Forward); + + NEW: Editor can now return number of submitted nodes (#81) + New API: + int GetNodeCount(); // Returns number of submitted nodes since Begin() call + + NEW: Editor can now return nodes in order they are drawn (#81) + New API: + int GetOrderedNodeIds(NodeId* nodes, int size); // Fills an array with node id's in order they're drawn; up to 'size` elements are set. Returns actual size of filled id's. + + NEW: Editor now allow to set Z position for nodes (#109) + + Nodes with higher Z position are drawn on top of ones with lower. + + New API: + void SetNodeZPosition(NodeId nodeId, float z); // Sets node z position, nodes with higher value are drawn over nodes with lower value + float GetNodeZPosition(NodeId nodeId); // Returns node z position, defaults is 0.0f + + NEW: Editor: SaveReasonFlags now inform about node creation/deletion + + NEW: Editor: Expose button index background was clicked with + New API: + ImGuiMouseButton GetBackgroundClickButtonIndex(); // -1 if none + ImGuiMouseButton GetBackgroundDoubleClickButtonIndex(); // -1 if none + + NEW: Editor: Expose configuration editor was created with + New API: + const Config& GetConfig(EditorContext* ctx = nullptr); + + NEW: Editor: Add highlighting of Links connected to selected Node (#175) + New API: + StyleColor_HighlightLinkBorder + StyleVar_HighlightConnectedLinks + + NEW: Editor: Add ability to snap link origin to pin direction (#167) + New API: + StyleVar_SnapLinkToPinDir + + NEW: Editor: Add way to override default zoom levels (#174) + New API: + ImVector Config::CustomZoomLevels; + + NEW: Editor: Add canvas size mode (#170) + + Config can now decide how editor should resize view when changing size. + + New API: + enum class CanvasSizeMode; + Config::CanvasSizeMode; + + BUGFIX: Avoid crash while destroying editor. + + BUGFIX: Save draw list used by editor between Begin() and End() + There is a chance ImGui::GetWindowDrawList() will return different draw list + while nodes are being composed. To avoid troubles of manipulating incorrect + draw list one obtained in Begin() is remembered and used. + + BUGFIX: Avoid division by zero in ImCubicBezierBoundingRect + + BUGFIX: Don't stuck in delete action if user does not handle it + + BUGFIX: Enable use channel splitter inside Begin/End for node and pin. #28 + + BUGFIX: Don't manipulate channels when editor is suspended #28 + + BUGFIX: Fix ObjectId serialization + + BUGFIX: GroupNode resize instead of move on low zoom #87 + + BUGFIX: Make Canvas work with Viewports (#91, #90) + + BUGFIX: Explicitly choose embedded GL3W as OpenGL extension loader (#96) + + BUGFIX: Application: Don't apply HiDPI logic for (-FLT_MAX,-FLT_MAX) mouse position + + BUGFIX: Editor: Clamp over-the-edge drag distance to prevent scrolling to infinity on focus lost + + BUGFIX: Editor: Consume scroll event (#73) (require ImGui 17909 or later) + + BUGFIX: Editor: Respect window focus while handling actions (#99) + + BUGFIX: Examples: Correct case of `data` directory (#97) + + BUGFIX: Canvas: Save/Restore CursorMaxPos only in Begin/End (#101) + + BUGFIX: Editor: Don't implicitly capture keyboard (#83) + + BUGFIX: Application: Reset key down state after loosing keyboard focus + + BUGFIX: Editor: Make off-screen dragging work again + + BUGFIX: ImGui: Disable obsolete functions (#103) + + BUGFIX: Editor: Allow nodes with zero size (#134) + + BUGFIX: Canvas: Update call ImGui::IsClippedEx() on ImGui > 18415 (#138) + + BUGFIX: Canvas: Disable pink debug outline around widget (#150) + + BUGFIX: Editor: Remove node settings when it is explicitly deleted (#153) + + BUGFIX: Editor: Improve link dragging with fast movement (#156) + + BUGFIX: Editor: Make selection rect start at click point + + BUGFIX: Editor: Make selection rect sharp + + BUGFIX: Editor: Don't populate unused channels with empty draw command, fixes memory leak (#168, #165) + + BUGFIX: Application: Correctly set DX11 View for NULL textures (#162) + + BUGFIX: Application: Recreate DX11 resources lazily (related to #162) + + BUGFIX: Editor: Don't steal input from active user widget (#172) + + BUGFIX: Editor: Delete item from internal list only when action accepts (#178) + + BUGFIX: Editor: Cycle canvas to correctly restore view on first frame (#159) + + BUGFIX: Editor: Don't relay on ImGui CursorMaxPos to apply padding (https://github.com/ocornut/imgui/issues/5548) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/docs/README.md b/cpp/vendor/imgui-node-editor/docs/README.md new file mode 100644 index 00000000..8a33c653 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/docs/README.md @@ -0,0 +1,169 @@ +# Node Editor in ImGui + +[![build](https://github.com/thedmd/imgui-node-editor/actions/workflows/build.yml/badge.svg)](https://github.com/thedmd/imgui-node-editor/actions/workflows/build.yml) + +## About + +An implementation of node editor with ImGui-like API. + +Project purpose is to serve as a basis for more complex solutions like blueprint editors. + +![node_editor_overview](https://user-images.githubusercontent.com/1197433/89328475-c01bc680-d68d-11ea-88bf-8c4155480927.gif) + +Node Editor is build around an idea "draw your content, we do the rest", which mean interactions are handled by editor, content rendering is handled by user. Editor will take care of: + * placing your node in the word + * dragging nodes + * zoom and scrolling + * selection + * various interaction that can be queried by API (creation, deletion, selection changes, etc.) + +Here are some highlights: + * Node movement and selection is handled internally + * Node and pin contents are fully customizable + * Fully styled, default theme is modeled after UE4 blueprints + - Flexible enough to produce such nodes: + + ![image](https://user-images.githubusercontent.com/1197433/60381408-c3895b00-9a54-11e9-8312-d9fc9af63347.png) + ![image](https://user-images.githubusercontent.com/1197433/60381400-a3599c00-9a54-11e9-9c51-a88f25f7db07.png) + ![image](https://user-images.githubusercontent.com/1197433/60381589-7d81c680-9a57-11e9-87b1-9f73ec33bea4.png) + - Customizable links based on Bézier curves: + + ![image](https://user-images.githubusercontent.com/1197433/60381475-ac973880-9a55-11e9-9ad9-5862975cd2b8.png) + ![image](https://user-images.githubusercontent.com/1197433/60381467-9db08600-9a55-11e9-9868-2ae849f67de9.png) + ![image](https://user-images.githubusercontent.com/1197433/60381488-cd5f8e00-9a55-11e9-8346-1f4c8d6bea22.png) + * Automatic highlights for nodes, pins and links: + + ![image](https://user-images.githubusercontent.com/1197433/60381536-9e95e780-9a56-11e9-80bb-dad0d3d9557a.png) + * Smooth navigation and selection + * Node state can be saved in user context, so layout will not break + * Selection rectangles and group dragging + * Context menu support + * Basic shortcuts support (cut/copy/paste/delete) + * ImGui style API + +Editor is used to implement blueprint editor in Spark CE engine, it proved itself there by allowing to do everything we needed. Therefore it is now slowly moving into stable state from beeing a prototype. + +Note: Project recently was restructured to mimic ImGui layout. + +Please report issues or questions if something isn't clear. + +## Dependencies + + * Vanilla ImGui 1.72+ + * C++14 + +### Dependencies for examples: + * https://github.com/thedmd/imgui/tree/feature/layout (used in blueprints sample only) + +### Optional extension you can pull into your local copy of ImGui node editor can take advantage of: + * ~~https://github.com/thedmd/imgui/tree/feature/draw-list-fringe-scale (for sharp rendering, while zooming)~~ It is part of ImGui since 1.80 release + * https://github.com/thedmd/imgui/tree/feature/extra-keys (for extra shortcuts) + +## Building / Installing + +Node Editor sources are located in root project directory. To use it, simply copy&paste sources into your project. Exactly like you can do with ImGui. + +### Examples +[Examples](../examples) can be build with CMake: +``` +Windows: + cmake -S examples -B build -G "Visual Studio 15 2017 Win64" + or + cmake -S examples -B build -G "Visual Studio 16 2019" -A x64 + +macOS: + cmake -S examples -B build -G "Xcode" + +Linux: + cmake -S examples -B build -G "Unix Makefiles" + +Build: + cmake --build build --config Release +``` +Executables will be located in `build\bin` directory. + +### Quick Start + +Main node editor header is located in [imgui_node_editor.h](../imgui_node_editor.h). + +Minimal example of one node can be found in [simple-example.cpp](../examples/simple-example/simple-example.cpp). +Press 'F' in editor to focus on editor content if you see only grid. +```cpp +# include +# include +# include + +namespace ed = ax::NodeEditor; + +struct Example: + public Application +{ + using Application::Application; + + void OnStart() override + { + ed::Config config; + config.SettingsFile = "Simple.json"; + m_Context = ed::CreateEditor(&config); + } + + void OnStop() override + { + ed::DestroyEditor(m_Context); + } + + void OnFrame(float deltaTime) override + { + auto& io = ImGui::GetIO(); + + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + + ImGui::Separator(); + + ed::SetCurrentEditor(m_Context); + ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + int uniqueId = 1; + // Start drawing nodes. + ed::BeginNode(uniqueId++); + ImGui::Text("Node A"); + ed::BeginPin(uniqueId++, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ed::BeginPin(uniqueId++, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + ed::EndNode(); + ed::End(); + ed::SetCurrentEditor(nullptr); + + //ImGui::ShowMetricsWindow(); + } + + ed::EditorContext* m_Context = nullptr; +}; + +int Main(int argc, char** argv) +{ + Example exampe("Simple", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} +``` + +Result: + +![00-Simple](https://user-images.githubusercontent.com/1197433/89328516-cca01f00-d68d-11ea-9959-2da159851101.png) + +For more details please visit [examples](../examples) folder. + +### Blueprints Example + +![Preview2](https://user-images.githubusercontent.com/1197433/60053458-2f2b9b00-96d8-11e9-92f9-08aff63b2023.png) + +### Here is Node Editor at work in Spark CE +![image](https://user-images.githubusercontent.com/1197433/60381756-174a7300-9a5a-11e9-9a04-00f10565e05e.png) +![image](https://user-images.githubusercontent.com/1197433/60381760-2f21f700-9a5a-11e9-9053-c0547a9cc40a.png) diff --git a/cpp/vendor/imgui-node-editor/docs/TODO.md b/cpp/vendor/imgui-node-editor/docs/TODO.md new file mode 100644 index 00000000..46bb2745 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/docs/TODO.md @@ -0,0 +1,14 @@ +In random order: +* Documentation: Make one + +Done: +* ~~ImGui: Factor out changes to ImGui to use vanilla version.~~ +* ~~Editor: Fix variable naming (mainly add `m_` prefix)~~ +* ~~Editor: Split NodeEditorImpl.cpp to multiple files, file has grown too big.~~ +* ~~Editor: Factor out use of `picojson.h`~~ +* ~~Editor: Move use of `` to optional code extensions~~ + + + + +#57 - join `ax::NodeEditor::EditorContext` with `struct EditorContext` and remove `reinterpret_cast<>` \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/CMakeLists.txt b/cpp/vendor/imgui-node-editor/examples/CMakeLists.txt new file mode 100644 index 00000000..20604a93 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/CMakeLists.txt @@ -0,0 +1,134 @@ +cmake_minimum_required(VERSION 3.12) + +project(imgui-node-editor) + +# Define IMGUI_NODE_EDITOR_ROOT_DIR pointing to project root directory +get_filename_component(IMGUI_NODE_EDITOR_ROOT_DIR ${CMAKE_SOURCE_DIR}/.. ABSOLUTE CACHE) + +# Enable solution folders in Visual Studio and Folders in Xcode +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Point CMake where to look for module files. +list(APPEND CMAKE_MODULE_PATH ${IMGUI_NODE_EDITOR_ROOT_DIR}/misc/cmake-modules) + +# Node editor use C++14 +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED YES) + + + + + +# Macro that will configure an example application +macro(add_example_executable name) + project(${name}) + + set(_Example_Sources + ${ARGN} + ) + + #source_group("" FILES ${_Example_Sources}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${_Example_Sources}) + + file(GLOB _Example_CommonResources CONFIGURE_DEPENDS "${IMGUI_NODE_EDITOR_ROOT_DIR}/examples/data/*") + file(GLOB _Example_Resources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/data/*") + #message(FATAL_ERROR "_Example_Resources = ${_Example_Resources}") + + set(_Example_Type) + if (WIN32) + set(_Example_Type WIN32) + + set(ApplicationIcon ${IMGUI_NODE_EDITOR_ROOT_DIR}/examples/Application/Support/Icon.ico) + file(TO_NATIVE_PATH "${ApplicationIcon}" ApplicationIcon) + string(REPLACE "\\" "\\\\" ApplicationIcon "${ApplicationIcon}") + configure_file( + ${IMGUI_NODE_EDITOR_ROOT_DIR}/examples/Application/Support/Resource.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/Resource.rc + ) + source_group(TREE "${IMGUI_NODE_EDITOR_ROOT_DIR}/examples" FILES ${_Example_CommonResources}) + source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${_Example_Resources}) + list(APPEND _Example_Resources + ${CMAKE_CURRENT_BINARY_DIR}/Resource.rc + ${_Example_CommonResources} + ) + source_group("resources" FILES ${CMAKE_CURRENT_BINARY_DIR}/Resource.rc) + elseif (APPLE) + set(_Example_Type MACOSX_BUNDLE) + + set_source_files_properties(${_Example_Resources} ${_Example_CommonResources} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources/data" + ) + set(_Example_Icon "${IMGUI_NODE_EDITOR_ROOT_DIR}/examples/application/support/Icon.icns") + list(APPEND _Example_Resources ${_Example_Icon}) + set_source_files_properties(${_Example_Icon} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources" + ) + endif() + + add_executable(${name} ${_Example_Type} ${_Example_Sources} ${_Example_Resources} ${_Example_CommonResources}) + + find_package(imgui REQUIRED) + find_package(imgui_node_editor REQUIRED) + target_link_libraries(${name} PRIVATE imgui imgui_node_editor application) + + set(_ExampleBinDir ${CMAKE_BINARY_DIR}/bin) + + set_target_properties(${name} PROPERTIES + FOLDER "examples" + RUNTIME_OUTPUT_DIRECTORY "${_ExampleBinDir}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${_ExampleBinDir}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${_ExampleBinDir}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${_ExampleBinDir}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${_ExampleBinDir}" + DEBUG_POSTFIX _d + RELWITHDEBINGO_POSTFIX _rd + MINSIZEREL_POSTFIX _r + VS_DEBUGGER_WORKING_DIRECTORY ${_ExampleBinDir} + MACOSX_BUNDLE_INFO_PLIST "${IMGUI_NODE_EDITOR_ROOT_DIR}/examples/application/support/Info.plist.in" + MACOSX_BUNDLE_BUNDLE_NAME "${PACKAGE_NAME}" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.sandbox.collisions" + MACOSX_BUNDLE_LONG_VERSION_STRING "${PACKAGE_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}" + MACOSX_BUNDLE_ICON_FILE Icon.icns + ) + + add_custom_command( + TARGET ${name} + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ARGS ${_ExampleBinDir}/data + ) + + set(_ResourceRoot ${CMAKE_CURRENT_SOURCE_DIR}) + foreach(_Resource ROOT "${IMGUI_NODE_EDITOR_ROOT_DIR}/examples/data" ${_Example_CommonResources} ROOT "${CMAKE_CURRENT_SOURCE_DIR}/data" ${_Example_Resources}) + if (_Resource STREQUAL ROOT) + set(_ResourceRoot FALSE) + continue() + elseif(NOT _ResourceRoot) + set(_ResourceRoot ${_Resource}) + continue() + endif() + + if ("${_Resource}" MATCHES "\.DS_Store$") + list(REMOVE_ITEM _Example_Resources ${_Resource}) + list(REMOVE_ITEM _Example_CommonResources ${_Resource}) + continue() + endif() + + file(RELATIVE_PATH _RelResource ${_ResourceRoot} ${_Resource}) + + add_custom_command( + TARGET ${name} + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ARGS ${_Resource} ${_ExampleBinDir}/data/${_RelResource} + ) + endforeach() + +endmacro() + +add_subdirectory(application) + +add_subdirectory(canvas-example) +add_subdirectory(simple-example) +add_subdirectory(widgets-example) +add_subdirectory(basic-interaction-example) +add_subdirectory(blueprints-example) diff --git a/cpp/vendor/imgui-node-editor/examples/application/CMakeLists.txt b/cpp/vendor/imgui-node-editor/examples/application/CMakeLists.txt new file mode 100644 index 00000000..7aa684b7 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/CMakeLists.txt @@ -0,0 +1,109 @@ +project(application) + +set(_Application_Sources + include/application.h + source/application.cpp + source/entry_point.cpp + source/imgui_extra_keys.h + source/config.h.in + source/setup.h + source/platform.h + source/platform_win32.cpp + source/platform_glfw.cpp + source/renderer.h + source/renderer_dx11.cpp + source/renderer_ogl3.cpp +) + +add_library(application STATIC) + +target_include_directories(application PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +find_package(imgui REQUIRED) +find_package(stb_image REQUIRED) +find_package(ScopeGuard REQUIRED) +target_link_libraries(application PUBLIC imgui) +target_link_libraries(application PRIVATE stb_image ScopeGuard) + +if (WIN32) + list(APPEND _Application_Sources + source/imgui_impl_dx11.cpp + source/imgui_impl_dx11.h + source/imgui_impl_win32.cpp + source/imgui_impl_win32.h + ) + + set(_DXSDK_Dir ${IMGUI_NODE_EDITOR_ROOT_DIR}/external/DXSDK) + set(_DXSDK_Arch x86) + if (${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set(_DXSDK_Arch x64) + endif() + + add_library(dxerr STATIC ${_DXSDK_Dir}/src/dxerr.cpp) + target_include_directories(dxerr PUBLIC "${_DXSDK_Dir}/include") + set_property(TARGET dxerr PROPERTY FOLDER "external") + + add_library(d3dx11 UNKNOWN IMPORTED) + set_target_properties(d3dx11 PROPERTIES + IMPORTED_LOCATION "${_DXSDK_Dir}/lib/${_DXSDK_Arch}/d3dx11.lib" + IMPORTED_LOCATION_DEBUG "${_DXSDK_Dir}/lib/${_DXSDK_Arch}/d3dx11d.lib" + INTERFACE_INCLUDE_DIRECTORIES "${_DXSDK_Dir}/include" + INTERFACE_LINK_LIBRARIES "$<$:dxerr>" + ) + + target_link_libraries(application PRIVATE d3d11.lib d3dcompiler.lib d3dx11) +else() + find_package(OpenGL REQUIRED) + find_package(glfw3 3 REQUIRED) + + if (APPLE) + target_link_libraries(application PRIVATE + "-framework CoreFoundation" + "-framework Cocoa" + "-framework IOKit" + "-framework CoreVideo" + ) + endif() +endif() + +if (OpenGL_FOUND) + set(HAVE_OPENGL YES) + + target_include_directories(application PRIVATE ${OPENGL_INCLUDE_DIR}) + target_link_libraries(application PRIVATE ${OPENGL_gl_LIBRARY}) + list(APPEND _Application_Sources + source/imgui_impl_opengl3.cpp + source/imgui_impl_opengl3.h + source/imgui_impl_opengl3_loader.h + ) +endif() + +if (glfw3_FOUND) + set(HAVE_GLFW3 YES) + + list(APPEND _Application_Sources + source/imgui_impl_glfw.cpp + source/imgui_impl_glfw.h + ) + target_link_libraries(application PRIVATE + glfw + ) +endif() + +configure_file( + source/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/source/config.h +) + +target_compile_definitions(application PRIVATE + #BACKEND_CONFIG=IMGUI_GLFW + #RENDERER_CONFIG=IMGUI_OGL3 +) + +target_include_directories(application PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/source) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${_Application_Sources}) + +target_sources(application PRIVATE ${_Application_Sources}) + +set_property(TARGET application PROPERTY FOLDER "examples") \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/include/application.h b/cpp/vendor/imgui-node-editor/examples/application/include/application.h new file mode 100644 index 00000000..88a39bdd --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/include/application.h @@ -0,0 +1,57 @@ +# pragma once +# include +# include +# include + +struct Platform; +struct Renderer; + +struct Application +{ + Application(const char* name); + Application(const char* name, int argc, char** argv); + ~Application(); + + bool Create(int width = -1, int height = -1); + + int Run(); + + void SetTitle(const char* title); + + bool Close(); + void Quit(); + + const std::string& GetName() const; + + ImFont* DefaultFont() const; + ImFont* HeaderFont() const; + + ImTextureID LoadTexture(const char* path); + ImTextureID CreateTexture(const void* data, int width, int height); + void DestroyTexture(ImTextureID texture); + int GetTextureWidth(ImTextureID texture); + int GetTextureHeight(ImTextureID texture); + + virtual void OnStart() {} + virtual void OnStop() {} + virtual void OnFrame(float deltaTime) {} + + virtual ImGuiWindowFlags GetWindowFlags() const; + + virtual bool CanClose() { return true; } + +private: + void RecreateFontAtlas(); + + void Frame(); + + std::string m_Name; + std::string m_IniFilename; + std::unique_ptr m_Platform; + std::unique_ptr m_Renderer; + ImGuiContext* m_Context = nullptr; + ImFont* m_DefaultFont = nullptr; + ImFont* m_HeaderFont = nullptr; +}; + +int Main(int argc, char** argv); \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/application.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/application.cpp new file mode 100644 index 00000000..aa1b3261 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/application.cpp @@ -0,0 +1,244 @@ +# include "application.h" +# include "setup.h" +# include "platform.h" +# include "renderer.h" + +extern "C" { +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#include "stb_image.h" +} + + +Application::Application(const char* name) + : Application(name, 0, nullptr) +{ +} + +Application::Application(const char* name, int argc, char** argv) + : m_Name(name) + , m_Platform(CreatePlatform(*this)) + , m_Renderer(CreateRenderer()) +{ + m_Platform->ApplicationStart(argc, argv); +} + +Application::~Application() +{ + m_Renderer->Destroy(); + + m_Platform->ApplicationStop(); + + if (m_Context) + { + ImGui::DestroyContext(m_Context); + m_Context= nullptr; + } +} + +bool Application::Create(int width /*= -1*/, int height /*= -1*/) +{ + m_Context = ImGui::CreateContext(); + ImGui::SetCurrentContext(m_Context); + + if (!m_Platform->OpenMainWindow("Application", width, height)) + return false; + + if (!m_Renderer->Create(*m_Platform)) + return false; + + m_IniFilename = m_Name + ".ini"; + + ImGuiIO& io = ImGui::GetIO(); + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.IniFilename = m_IniFilename.c_str(); + io.LogFilename = nullptr; + + ImGui::StyleColorsDark(); + + RecreateFontAtlas(); + + m_Platform->AcknowledgeWindowScaleChanged(); + m_Platform->AcknowledgeFramebufferScaleChanged(); + + OnStart(); + + Frame(); + + return true; +} + +int Application::Run() +{ + m_Platform->ShowMainWindow(); + + while (m_Platform->ProcessMainWindowEvents()) + { + if (!m_Platform->IsMainWindowVisible()) + continue; + + Frame(); + } + + OnStop(); + + return 0; +} + +void Application::RecreateFontAtlas() +{ + ImGuiIO& io = ImGui::GetIO(); + + IM_DELETE(io.Fonts); + + io.Fonts = IM_NEW(ImFontAtlas); + + ImFontConfig config; + config.OversampleH = 4; + config.OversampleV = 4; + config.PixelSnapH = false; + + m_DefaultFont = io.Fonts->AddFontFromFileTTF("data/Play-Regular.ttf", 18.0f, &config); + m_HeaderFont = io.Fonts->AddFontFromFileTTF("data/Cuprum-Bold.ttf", 20.0f, &config); + + io.Fonts->Build(); +} + +void Application::Frame() +{ + auto& io = ImGui::GetIO(); + + if (m_Platform->HasWindowScaleChanged()) + m_Platform->AcknowledgeWindowScaleChanged(); + + if (m_Platform->HasFramebufferScaleChanged()) + { + RecreateFontAtlas(); + m_Platform->AcknowledgeFramebufferScaleChanged(); + } + + const float windowScale = m_Platform->GetWindowScale(); + const float framebufferScale = m_Platform->GetFramebufferScale(); + + if (io.WantSetMousePos) + { + io.MousePos.x *= windowScale; + io.MousePos.y *= windowScale; + } + + m_Platform->NewFrame(); + + // Don't touch "uninitialized" mouse position + if (io.MousePos.x > -FLT_MAX && io.MousePos.y > -FLT_MAX) + { + io.MousePos.x /= windowScale; + io.MousePos.y /= windowScale; + } + io.DisplaySize.x /= windowScale; + io.DisplaySize.y /= windowScale; + + io.DisplayFramebufferScale.x = framebufferScale; + io.DisplayFramebufferScale.y = framebufferScale; + + m_Renderer->NewFrame(); + + ImGui::NewFrame(); + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + const auto windowBorderSize = ImGui::GetStyle().WindowBorderSize; + const auto windowRounding = ImGui::GetStyle().WindowRounding; + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::Begin("Content", nullptr, GetWindowFlags()); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, windowBorderSize); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, windowRounding); + + OnFrame(io.DeltaTime); + + ImGui::PopStyleVar(2); + ImGui::End(); + ImGui::PopStyleVar(2); + + // Rendering + m_Renderer->Clear(ImColor(32, 32, 32, 255)); + ImGui::Render(); + m_Renderer->RenderDrawData(ImGui::GetDrawData()); + + m_Platform->FinishFrame(); +} + +void Application::SetTitle(const char* title) +{ + m_Platform->SetMainWindowTitle(title); +} + +bool Application::Close() +{ + return m_Platform->CloseMainWindow(); +} + +void Application::Quit() +{ + m_Platform->Quit(); +} + +const std::string& Application::GetName() const +{ + return m_Name; +} + +ImFont* Application::DefaultFont() const +{ + return m_DefaultFont; +} + +ImFont* Application::HeaderFont() const +{ + return m_HeaderFont; +} + +ImTextureID Application::LoadTexture(const char* path) +{ + int width = 0, height = 0, component = 0; + if (auto data = stbi_load(path, &width, &height, &component, 4)) + { + auto texture = CreateTexture(data, width, height); + stbi_image_free(data); + return texture; + } + else + return nullptr; +} + +ImTextureID Application::CreateTexture(const void* data, int width, int height) +{ + return m_Renderer->CreateTexture(data, width, height); +} + +void Application::DestroyTexture(ImTextureID texture) +{ + m_Renderer->DestroyTexture(texture); +} + +int Application::GetTextureWidth(ImTextureID texture) +{ + return m_Renderer->GetTextureWidth(texture); +} + +int Application::GetTextureHeight(ImTextureID texture) +{ + return m_Renderer->GetTextureHeight(texture); +} + +ImGuiWindowFlags Application::GetWindowFlags() const +{ + return + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoBringToFrontOnFocus; +} diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/config.h.in b/cpp/vendor/imgui-node-editor/examples/application/source/config.h.in new file mode 100644 index 00000000..07973c76 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/config.h.in @@ -0,0 +1,4 @@ +# pragma once + +# cmakedefine01 HAVE_GLFW3 +# cmakedefine01 HAVE_OPENGL \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/entry_point.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/entry_point.cpp new file mode 100644 index 00000000..cbf6a23c --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/entry_point.cpp @@ -0,0 +1,21 @@ +# include "application.h" +# include "platform.h" + +# if PLATFORM(WINDOWS) +# define NOMINMAX +# define WIN32_LEAN_AND_MEAN +# include +# include // __argc, argv +# endif + +# if defined(_WIN32) && !defined(_CONSOLE) +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) +{ + return Main(__argc, __argv); +} +# else +int main(int argc, char** argv) +{ + return Main(argc, argv); +} +# endif diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_extra_keys.h b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_extra_keys.h new file mode 100644 index 00000000..2135b402 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_extra_keys.h @@ -0,0 +1,65 @@ +# pragma once +# include + +# if !defined(IMGUI_VERSION_NUM) || (IMGUI_VERSION_NUM < 18822) + +# include + +// https://stackoverflow.com/a/8597498 +# define DECLARE_HAS_NESTED(Name, Member) \ + \ + template \ + struct has_nested_ ## Name \ + { \ + typedef char yes; \ + typedef yes(&no)[2]; \ + \ + template static yes test(decltype(U::Member)*); \ + template static no test(...); \ + \ + static bool const value = sizeof(test(0)) == sizeof(yes); \ + }; + +# define DECLARE_KEY_TESTER(Key) \ + DECLARE_HAS_NESTED(Key, Key) \ + struct KeyTester_ ## Key \ + { \ + template \ + static int Get(typename std::enable_if::value, T>::type*) \ + { \ + return T::Key; \ + } \ + \ + template \ + static int Get(typename std::enable_if::value, T>::type*) \ + { \ + return -1; \ + } \ + } + +DECLARE_KEY_TESTER(ImGuiKey_F); +DECLARE_KEY_TESTER(ImGuiKey_D); + +static inline int GetEnumValueForF() +{ + return KeyTester_ImGuiKey_F::Get(nullptr); +} + +static inline int GetEnumValueForD() +{ + return KeyTester_ImGuiKey_D::Get(nullptr); +} + +# else + +static inline ImGuiKey GetEnumValueForF() +{ + return ImGuiKey_F; +} + +static inline ImGuiKey GetEnumValueForD() +{ + return ImGuiKey_D; +} + +# endif \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.cpp new file mode 100644 index 00000000..a91fd9ce --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.cpp @@ -0,0 +1,681 @@ +// dear imgui: Renderer for DirectX11 +// This needs to be used along with a Platform Binding (e.g. Win32) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile(). +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility. +// 2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions. +// 2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example. +// 2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2016-05-07: DirectX11: Disabling depth-write. + +#include "imgui.h" +#include "imgui_impl_dx11.h" + +// DirectX +struct IUnknown; +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. +#endif + +struct TEXTURE; + +// DirectX data +static ID3D11Device* g_pd3dDevice = NULL; +static ID3D11DeviceContext* g_pd3dDeviceContext = NULL; +static IDXGIFactory* g_pFactory = NULL; +static ID3D11Buffer* g_pVB = NULL; +static ID3D11Buffer* g_pIB = NULL; +static ID3D10Blob* g_pVertexShaderBlob = NULL; +static ID3D11VertexShader* g_pVertexShader = NULL; +static ID3D11InputLayout* g_pInputLayout = NULL; +static ID3D11Buffer* g_pVertexConstantBuffer = NULL; +static ID3D10Blob* g_pPixelShaderBlob = NULL; +static ID3D11PixelShader* g_pPixelShader = NULL; +static ID3D11SamplerState* g_pFontSampler = NULL; +static ImTextureID g_pFontTextureID = NULL; +static ID3D11RasterizerState* g_pRasterizerState = NULL; +static ID3D11BlendState* g_pBlendState = NULL; +static ID3D11DepthStencilState* g_pDepthStencilState = NULL; +static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000; +static ImVector g_Textures; + +struct VERTEX_CONSTANT_BUFFER +{ + float mvp[4][4]; +}; + +struct TEXTURE +{ + TEXTURE() + { + View = NULL; + Width = 0; + Height = 0; + } + + ID3D11ShaderResourceView* View; + int Width; + int Height; + ImVector Data; +}; + +// Forward Declarations +static bool ImGui_UploadTexture(TEXTURE* texture); +static void ImGui_ReleaseTexture(TEXTURE* texture); + +static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx) +{ + // Setup viewport + D3D11_VIEWPORT vp; + memset(&vp, 0, sizeof(D3D11_VIEWPORT)); + vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; + vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.MinDepth = 0.0f; + vp.MaxDepth = 1.0f; + vp.TopLeftX = vp.TopLeftY = 0; + ctx->RSSetViewports(1, &vp); + + // Setup shader and vertex buffers + unsigned int stride = sizeof(ImDrawVert); + unsigned int offset = 0; + ctx->IASetInputLayout(g_pInputLayout); + ctx->IASetVertexBuffers(0, 1, &g_pVB, &stride, &offset); + ctx->IASetIndexBuffer(g_pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + ctx->VSSetShader(g_pVertexShader, NULL, 0); + ctx->VSSetConstantBuffers(0, 1, &g_pVertexConstantBuffer); + ctx->PSSetShader(g_pPixelShader, NULL, 0); + ctx->PSSetSamplers(0, 1, &g_pFontSampler); + + // Setup blend state + const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + ctx->OMSetBlendState(g_pBlendState, blend_factor, 0xffffffff); + ctx->OMSetDepthStencilState(g_pDepthStencilState, 0); + ctx->RSSetState(g_pRasterizerState); +} + +// Render function +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized + if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) + return; + + ID3D11DeviceContext* ctx = g_pd3dDeviceContext; + + // Create and grow vertex/index buffers if needed + if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) + { + if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } + g_VertexBufferSize = draw_data->TotalVtxCount + 5000; + D3D11_BUFFER_DESC desc; + memset(&desc, 0, sizeof(D3D11_BUFFER_DESC)); + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.ByteWidth = g_VertexBufferSize * sizeof(ImDrawVert); + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + desc.MiscFlags = 0; + if (g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pVB) < 0) + return; + } + if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) + { + if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } + g_IndexBufferSize = draw_data->TotalIdxCount + 10000; + D3D11_BUFFER_DESC desc; + memset(&desc, 0, sizeof(D3D11_BUFFER_DESC)); + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.ByteWidth = g_IndexBufferSize * sizeof(ImDrawIdx); + desc.BindFlags = D3D11_BIND_INDEX_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + if (g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pIB) < 0) + return; + } + + // Upload vertex/index data into a single contiguous GPU buffer + D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource; + if (ctx->Map(g_pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK) + return; + if (ctx->Map(g_pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK) + return; + ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData; + ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + vtx_dst += cmd_list->VtxBuffer.Size; + idx_dst += cmd_list->IdxBuffer.Size; + } + ctx->Unmap(g_pVB, 0); + ctx->Unmap(g_pIB, 0); + + // Setup orthographic projection matrix into our constant buffer + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + { + D3D11_MAPPED_SUBRESOURCE mapped_resource; + if (ctx->Map(g_pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK) + return; + VERTEX_CONSTANT_BUFFER* constant_buffer = (VERTEX_CONSTANT_BUFFER*)mapped_resource.pData; + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + float mvp[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.5f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, + }; + memcpy(&constant_buffer->mvp, mvp, sizeof(mvp)); + ctx->Unmap(g_pVertexConstantBuffer, 0); + } + + // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!) + struct BACKUP_DX11_STATE + { + UINT ScissorRectsCount, ViewportsCount; + D3D11_RECT ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + D3D11_VIEWPORT Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + ID3D11RasterizerState* RS; + ID3D11BlendState* BlendState; + FLOAT BlendFactor[4]; + UINT SampleMask; + UINT StencilRef; + ID3D11DepthStencilState* DepthStencilState; + ID3D11ShaderResourceView* PSShaderResource; + ID3D11SamplerState* PSSampler; + ID3D11PixelShader* PS; + ID3D11VertexShader* VS; + UINT PSInstancesCount, VSInstancesCount; + ID3D11ClassInstance* PSInstances[256], *VSInstances[256]; // 256 is max according to PSSetShader documentation + D3D11_PRIMITIVE_TOPOLOGY PrimitiveTopology; + ID3D11Buffer* IndexBuffer, *VertexBuffer, *VSConstantBuffer; + UINT IndexBufferOffset, VertexBufferStride, VertexBufferOffset; + DXGI_FORMAT IndexBufferFormat; + ID3D11InputLayout* InputLayout; + }; + BACKUP_DX11_STATE old; + old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE; + ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects); + ctx->RSGetViewports(&old.ViewportsCount, old.Viewports); + ctx->RSGetState(&old.RS); + ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask); + ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef); + ctx->PSGetShaderResources(0, 1, &old.PSShaderResource); + ctx->PSGetSamplers(0, 1, &old.PSSampler); + old.PSInstancesCount = old.VSInstancesCount = 256; + ctx->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount); + ctx->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount); + ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer); + ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology); + ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset); + ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); + ctx->IAGetInputLayout(&old.InputLayout); + + // Setup desired DX state + ImGui_ImplDX11_SetupRenderState(draw_data, ctx); + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int global_idx_offset = 0; + int global_vtx_offset = 0; + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != NULL) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplDX11_SetupRenderState(draw_data, ctx); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + ImVec4 clip_rect; + clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; + clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; + clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; + clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; + + // Apply scissor/clipping rectangle + const D3D11_RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; + ctx->RSSetScissorRects(1, &r); + + // Bind texture, Draw + TEXTURE* texture = (TEXTURE*)pcmd->TextureId; + ID3D11ShaderResourceView* textureView = texture ? texture->View : nullptr; + ctx->PSSetShaderResources(0, 1, &textureView); + ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset); + } + } + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + + // Restore modified DX state + ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects); + ctx->RSSetViewports(old.ViewportsCount, old.Viewports); + ctx->RSSetState(old.RS); if (old.RS) old.RS->Release(); + ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release(); + ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release(); + ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release(); + ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release(); + ctx->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release(); + for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release(); + ctx->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release(); + ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release(); + for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release(); + ctx->IASetPrimitiveTopology(old.PrimitiveTopology); + ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release(); + ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release(); + ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release(); +} + +static void ImGui_ImplDX11_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + g_pFontTextureID = ImGui_CreateTexture(pixels, width, height); + + io.Fonts->TexID = g_pFontTextureID; + + + // Create texture sampler + { + D3D11_SAMPLER_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + desc.MipLODBias = 0.f; + desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + desc.MinLOD = 0.f; + desc.MaxLOD = 0.f; + g_pd3dDevice->CreateSamplerState(&desc, &g_pFontSampler); + } +} + +bool ImGui_ImplDX11_CreateDeviceObjects() +{ + if (!g_pd3dDevice) + return false; + if (g_pFontSampler) + ImGui_ImplDX11_InvalidateDeviceObjects(); + + // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) + // If you would like to use this DX11 sample code but remove this dependency you can: + // 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution] + // 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL. + // See https://github.com/ocornut/imgui/pull/638 for sources and details. + + // Create the vertex shader + { + static const char* vertexShader = + "cbuffer vertexBuffer : register(b0) \ + {\ + float4x4 ProjectionMatrix; \ + };\ + struct VS_INPUT\ + {\ + float2 pos : POSITION;\ + float4 col : COLOR0;\ + float2 uv : TEXCOORD0;\ + };\ + \ + struct PS_INPUT\ + {\ + float4 pos : SV_POSITION;\ + float4 col : COLOR0;\ + float2 uv : TEXCOORD0;\ + };\ + \ + PS_INPUT main(VS_INPUT input)\ + {\ + PS_INPUT output;\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.col = input.col;\ + output.uv = input.uv;\ + return output;\ + }"; + + D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &g_pVertexShaderBlob, NULL); + if (g_pVertexShaderBlob == NULL) // NB: Pass ID3D10Blob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! + return false; + if (g_pd3dDevice->CreateVertexShader((DWORD*)g_pVertexShaderBlob->GetBufferPointer(), g_pVertexShaderBlob->GetBufferSize(), NULL, &g_pVertexShader) != S_OK) + return false; + + // Create the input layout + D3D11_INPUT_ELEMENT_DESC local_layout[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (size_t)(&((ImDrawVert*)0)->pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (size_t)(&((ImDrawVert*)0)->uv), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (size_t)(&((ImDrawVert*)0)->col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + if (g_pd3dDevice->CreateInputLayout(local_layout, 3, g_pVertexShaderBlob->GetBufferPointer(), g_pVertexShaderBlob->GetBufferSize(), &g_pInputLayout) != S_OK) + return false; + + // Create the constant buffer + { + D3D11_BUFFER_DESC desc; + desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER); + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + desc.MiscFlags = 0; + g_pd3dDevice->CreateBuffer(&desc, NULL, &g_pVertexConstantBuffer); + } + } + + // Create the pixel shader + { + static const char* pixelShader = + "struct PS_INPUT\ + {\ + float4 pos : SV_POSITION;\ + float4 col : COLOR0;\ + float2 uv : TEXCOORD0;\ + };\ + sampler sampler0;\ + Texture2D texture0;\ + \ + float4 main(PS_INPUT input) : SV_Target\ + {\ + float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \ + out_col.rgb *= out_col.a; \ + return out_col; \ + }"; + + D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &g_pPixelShaderBlob, NULL); + if (g_pPixelShaderBlob == NULL) // NB: Pass ID3D10Blob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! + return false; + if (g_pd3dDevice->CreatePixelShader((DWORD*)g_pPixelShaderBlob->GetBufferPointer(), g_pPixelShaderBlob->GetBufferSize(), NULL, &g_pPixelShader) != S_OK) + return false; + } + + // Create the blending setup + { + D3D11_BLEND_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.AlphaToCoverageEnable = false; + desc.RenderTarget[0].BlendEnable = true; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + g_pd3dDevice->CreateBlendState(&desc, &g_pBlendState); + } + + // Create the rasterizer state + { + D3D11_RASTERIZER_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.FillMode = D3D11_FILL_SOLID; + desc.CullMode = D3D11_CULL_NONE; + desc.ScissorEnable = true; + desc.DepthClipEnable = true; + g_pd3dDevice->CreateRasterizerState(&desc, &g_pRasterizerState); + } + + // Create depth-stencil State + { + D3D11_DEPTH_STENCIL_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.DepthEnable = false; + desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + desc.DepthFunc = D3D11_COMPARISON_ALWAYS; + desc.StencilEnable = false; + desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + desc.BackFace = desc.FrontFace; + g_pd3dDevice->CreateDepthStencilState(&desc, &g_pDepthStencilState); + } + + ImGui_ImplDX11_CreateFontsTexture(); + + for (auto& texture : g_Textures) + ImGui_UploadTexture(texture); + + return true; +} + +void ImGui_ImplDX11_InvalidateDeviceObjects() +{ + if (!g_pd3dDevice) + return; + + for (auto& texture : g_Textures) + ImGui_ReleaseTexture(texture); + + if (g_pFontTextureID) + { + ImGui_DestroyTexture(g_pFontTextureID); + g_pFontTextureID = NULL; + } + + if (g_pFontSampler) { g_pFontSampler->Release(); g_pFontSampler = NULL; } + if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } + if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } + + if (g_pBlendState) { g_pBlendState->Release(); g_pBlendState = NULL; } + if (g_pDepthStencilState) { g_pDepthStencilState->Release(); g_pDepthStencilState = NULL; } + if (g_pRasterizerState) { g_pRasterizerState->Release(); g_pRasterizerState = NULL; } + if (g_pPixelShader) { g_pPixelShader->Release(); g_pPixelShader = NULL; } + if (g_pPixelShaderBlob) { g_pPixelShaderBlob->Release(); g_pPixelShaderBlob = NULL; } + if (g_pVertexConstantBuffer) { g_pVertexConstantBuffer->Release(); g_pVertexConstantBuffer = NULL; } + if (g_pInputLayout) { g_pInputLayout->Release(); g_pInputLayout = NULL; } + if (g_pVertexShader) { g_pVertexShader->Release(); g_pVertexShader = NULL; } + if (g_pVertexShaderBlob) { g_pVertexShaderBlob->Release(); g_pVertexShaderBlob = NULL; } +} + +bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context) +{ + // Setup back-end capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendRendererName = "imgui_impl_dx11"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + + // Get factory from device + IDXGIDevice* pDXGIDevice = NULL; + IDXGIAdapter* pDXGIAdapter = NULL; + IDXGIFactory* pFactory = NULL; + + if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK) + if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK) + if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK) + { + g_pd3dDevice = device; + g_pd3dDeviceContext = device_context; + g_pFactory = pFactory; + } + if (pDXGIDevice) pDXGIDevice->Release(); + if (pDXGIAdapter) pDXGIAdapter->Release(); + g_pd3dDevice->AddRef(); + g_pd3dDeviceContext->AddRef(); + + g_Textures.reserve(16); + + return true; +} + +void ImGui_ImplDX11_Shutdown() +{ + ImGui_ImplDX11_InvalidateDeviceObjects(); + if (g_pFactory) { g_pFactory->Release(); g_pFactory = NULL; } + if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; } + if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = NULL; } +} + +void ImGui_ImplDX11_NewFrame() +{ + if (!g_pFontSampler) + ImGui_ImplDX11_CreateDeviceObjects(); +} + +extern "C" { +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#include "stb_image.h" +} + +ImTextureID ImGui_LoadTexture(const char* path) +{ + int width = 0, height = 0, component = 0; + if (auto data = stbi_load(path, &width, &height, &component, 4)) + { + auto texture = ImGui_CreateTexture(data, width, height); + stbi_image_free(data); + return texture; + } + else + return nullptr; +} + +ImTextureID ImGui_CreateTexture(const void* data, int width, int height) +{ + auto texture = IM_NEW(TEXTURE); + texture->Width = width; + texture->Height = height; + texture->Data.resize(width * height * 4); + memcpy(texture->Data.Data, data, texture->Data.Size); + + if (!ImGui_UploadTexture(texture)) + { + IM_DELETE(texture); + return nullptr; + } + + g_Textures.push_back(texture); + + return (ImTextureID)texture; +} + +void ImGui_DestroyTexture(ImTextureID texture) +{ + if (!texture) + return; + + TEXTURE* texture_object = (TEXTURE*)(texture); + + ImGui_ReleaseTexture(texture_object); + + for (TEXTURE** it = g_Textures.begin(), **itEnd = g_Textures.end(); it != itEnd; ++it) + { + if (*it == texture_object) + { + g_Textures.erase(it); + break; + } + } + + IM_DELETE(texture_object); +} + +static bool ImGui_UploadTexture(TEXTURE* texture) +{ + if (!g_pd3dDevice || !texture) + return false; + + if (texture->View) + return true; + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = texture->Width; + desc.Height = texture->Height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + + D3D11_SUBRESOURCE_DATA subResource = {}; + subResource.pSysMem = texture->Data.Data; + subResource.SysMemPitch = desc.Width * 4; + subResource.SysMemSlicePitch = 0; + + ID3D11Texture2D *pTexture = nullptr; + g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + + if (!pTexture) + return false; + + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + + g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &texture->View); + pTexture->Release(); + + return true; +} + +static void ImGui_ReleaseTexture(TEXTURE* texture) +{ + if (texture) + { + if (texture->View) + { + texture->View->Release(); + texture->View = nullptr; + } + } +} + +int ImGui_GetTextureWidth(ImTextureID texture) +{ + if (TEXTURE* tex = (TEXTURE*)(texture)) + return tex->Width; + else + return 0; +} + +int ImGui_GetTextureHeight(ImTextureID texture) +{ + if (TEXTURE* tex = (TEXTURE*)(texture)) + return tex->Height; + else + return 0; +} \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.h b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.h new file mode 100644 index 00000000..eb032fc6 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_dx11.h @@ -0,0 +1,29 @@ +// dear imgui: Renderer for DirectX11 +// This needs to be used along with a Platform Binding (e.g. Win32) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +#pragma once + +struct ID3D11Device; +struct ID3D11DeviceContext; + +IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context); +IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects(); +IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects(); + +IMGUI_IMPL_API ImTextureID ImGui_LoadTexture(const char* path); +IMGUI_IMPL_API ImTextureID ImGui_CreateTexture(const void* data, int width, int height); +IMGUI_IMPL_API void ImGui_DestroyTexture(ImTextureID texture); +IMGUI_IMPL_API int ImGui_GetTextureWidth(ImTextureID texture); +IMGUI_IMPL_API int ImGui_GetTextureHeight(ImTextureID texture); diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.cpp new file mode 100644 index 00000000..e24bb3e1 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.cpp @@ -0,0 +1,382 @@ +// dear imgui: Platform Binding for GLFW +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) +// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) +// (Requires: GLFW 3.1+) + +// Implemented features: +// [X] Platform: Clipboard support. +// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+). +// [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE). + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors. +// 2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor). +// 2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown. +// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. +// 2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter(). +// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized. +// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. +// 2018-11-07: Inputs: When installing our GLFW callbacks, we save user's previously installed ones - if any - and chain call them. +// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls. +// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. +// 2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples. +// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag. +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()). +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set. +// 2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set). +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). +// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. + +#include "imgui.h" +#include "imgui_impl_glfw.h" +#include "imgui_extra_keys.h" + +// GLFW +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#include // for glfwGetWin32Window +#endif +#define GLFW_HAS_WINDOW_TOPMOST (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ GLFW_FLOATING +#define GLFW_HAS_WINDOW_HOVERED (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ GLFW_HOVERED +#define GLFW_HAS_WINDOW_ALPHA (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwSetWindowOpacity +#define GLFW_HAS_PER_MONITOR_DPI (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwGetMonitorContentScale +#define GLFW_HAS_VULKAN (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ glfwCreateWindowSurface +#ifdef GLFW_RESIZE_NESW_CURSOR // let's be nice to people who pulled GLFW between 2019-04-16 (3.4 define) and 2019-11-29 (cursors defines) // FIXME: Remove when GLFW 3.4 is released? +#define GLFW_HAS_NEW_CURSORS (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR +#else +#define GLFW_HAS_NEW_CURSORS (0) +#endif + +// Data +enum GlfwClientApi +{ + GlfwClientApi_Unknown, + GlfwClientApi_OpenGL, + GlfwClientApi_Vulkan +}; +static GLFWwindow* g_Window = NULL; // Main window +static GlfwClientApi g_ClientApi = GlfwClientApi_Unknown; +static double g_Time = 0.0; +static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {}; +static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; +static bool g_InstalledCallbacks = false; + +// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. +static GLFWmousebuttonfun g_PrevUserCallbackMousebutton = NULL; +static GLFWscrollfun g_PrevUserCallbackScroll = NULL; +static GLFWkeyfun g_PrevUserCallbackKey = NULL; +static GLFWcharfun g_PrevUserCallbackChar = NULL; + +static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data) +{ + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text) +{ + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) +{ + if (g_PrevUserCallbackMousebutton != NULL) + g_PrevUserCallbackMousebutton(window, button, action, mods); + + if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(g_MouseJustPressed)) + g_MouseJustPressed[button] = true; +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) +{ + if (g_PrevUserCallbackScroll != NULL) + g_PrevUserCallbackScroll(window, xoffset, yoffset); + + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheelH += (float)xoffset; + io.MouseWheel += (float)yoffset; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (g_PrevUserCallbackKey != NULL) + g_PrevUserCallbackKey(window, key, scancode, action, mods); + + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + + // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; +#ifdef _WIN32 + io.KeySuper = false; +#else + io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; +#endif +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c) +{ + if (g_PrevUserCallbackChar != NULL) + g_PrevUserCallbackChar(window, c); + + ImGuiIO& io = ImGui::GetIO(); + io.AddInputCharacter(c); +} + +static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api) +{ + g_Window = window; + g_Time = 0.0; + + // Setup back-end capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + io.BackendPlatformName = "imgui_impl_glfw"; + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_KeyPadEnter] = GLFW_KEY_KP_ENTER; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + int f_index = GetEnumValueForF(); + int d_index = GetEnumValueForD(); + if (f_index >= 0) + io.KeyMap[f_index] = GLFW_KEY_F; + if (d_index >= 0) + io.KeyMap[d_index] = GLFW_KEY_D; + + io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText; + io.ClipboardUserData = g_Window; +#if defined(_WIN32) + //io.ImeWindowHandle = (void*)glfwGetWin32Window(g_Window); +#endif + + // Create mouse cursors + // (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist, + // GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting. + // Missing cursors will return NULL and our _UpdateMouseCursor() function will use the Arrow cursor instead.) + GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL); + g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); +#if GLFW_HAS_NEW_CURSORS + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR); + g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR); +#else + g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); +#endif + glfwSetErrorCallback(prev_error_callback); + + // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. + g_PrevUserCallbackMousebutton = NULL; + g_PrevUserCallbackScroll = NULL; + g_PrevUserCallbackKey = NULL; + g_PrevUserCallbackChar = NULL; + if (install_callbacks) + { + g_InstalledCallbacks = true; + g_PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + g_PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + g_PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); + } + + g_ClientApi = client_api; + return true; +} + +bool ImGui_ImplGlfw_InitForNone(GLFWwindow* window, bool install_callbacks) +{ + return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown); +} + +bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks) +{ + return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL); +} + +bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks) +{ + return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan); +} + +void ImGui_ImplGlfw_Shutdown() +{ + if (g_InstalledCallbacks) + { + glfwSetMouseButtonCallback(g_Window, g_PrevUserCallbackMousebutton); + glfwSetScrollCallback(g_Window, g_PrevUserCallbackScroll); + glfwSetKeyCallback(g_Window, g_PrevUserCallbackKey); + glfwSetCharCallback(g_Window, g_PrevUserCallbackChar); + g_InstalledCallbacks = false; + } + + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) + { + glfwDestroyCursor(g_MouseCursors[cursor_n]); + g_MouseCursors[cursor_n] = NULL; + } + g_ClientApi = GlfwClientApi_Unknown; +} + +static void ImGui_ImplGlfw_UpdateMousePosAndButtons() +{ + // Update buttons + ImGuiIO& io = ImGui::GetIO(); + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) + { + // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; + g_MouseJustPressed[i] = false; + } + + // Update mouse position + const ImVec2 mouse_pos_backup = io.MousePos; + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); +#ifdef __EMSCRIPTEN__ + const bool focused = true; // Emscripten +#else + const bool focused = glfwGetWindowAttrib(g_Window, GLFW_FOCUSED) != 0; +#endif + if (focused) + { + if (io.WantSetMousePos) + { + glfwSetCursorPos(g_Window, (double)mouse_pos_backup.x, (double)mouse_pos_backup.y); + } + else + { + double mouse_x, mouse_y; + glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } +} + +static void ImGui_ImplGlfw_UpdateMouseCursor() +{ + ImGuiIO& io = ImGui::GetIO(); + if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(g_Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) + return; + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) + { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + else + { + // Show OS mouse cursor + // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here. + glfwSetCursor(g_Window, g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } +} + +static void ImGui_ImplGlfw_UpdateGamepads() +{ + ImGuiIO& io = ImGui::GetIO(); + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + + // Update gamepad inputs + #define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; } + #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; } + int axes_count = 0, buttons_count = 0; + const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + MAP_BUTTON(ImGuiNavInput_Activate, 0); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, 1); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, 2); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, 3); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, 13); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, 11); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, 10); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, 12); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, 4); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, 5); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, 0, -0.3f, -0.9f); + MAP_ANALOG(ImGuiNavInput_LStickRight,0, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickUp, 1, +0.3f, +0.9f); + MAP_ANALOG(ImGuiNavInput_LStickDown, 1, -0.3f, -0.9f); + #undef MAP_BUTTON + #undef MAP_ANALOG + if (axes_count > 0 && buttons_count > 0) + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + else + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; +} + +void ImGui_ImplGlfw_NewFrame() +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) + io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f); + g_Time = current_time; + + ImGui_ImplGlfw_UpdateMousePosAndButtons(); + ImGui_ImplGlfw_UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + ImGui_ImplGlfw_UpdateGamepads(); +} diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.h b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.h new file mode 100644 index 00000000..b051279c --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_glfw.h @@ -0,0 +1,36 @@ +// dear imgui: Platform Binding for GLFW +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) +// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] Platform: Clipboard support. +// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [x] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: 3 cursors types are missing from GLFW. +// [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE). + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// About GLSL version: +// The 'glsl_version' initialization parameter defaults to "#version 150" if NULL. +// Only override if your GL version doesn't handle this GLSL version. Keep NULL if unsure! + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +struct GLFWwindow; + +IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForNone(GLFWwindow* window, bool install_callbacks); +IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks); +IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks); +IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); + +// GLFW callbacks +// - When calling Init with 'install_callbacks=true': GLFW callbacks will be installed for you. They will call user's previously installed callbacks, if any. +// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call those function yourself from your own GLFW callbacks. +IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); +IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); +IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); +IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.cpp new file mode 100644 index 00000000..08cc84a1 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.cpp @@ -0,0 +1,947 @@ +// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). + +// About WebGL/ES: +// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. +// - This is done automatically on iOS, Android and Emscripten targets. +// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accomodating for NetBSD systems having only "libGL.so.3" available. (#6983) +// 2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445) +// 2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333) +// 2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375) +// 2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333) +// 2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224) +// 2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530) +// 2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224) +// 2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes). +// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. +// 2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'. +// 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127). +// 2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states. +// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers. +// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions. +// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state. +// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader. +// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version. +// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater. +// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer. +// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state. +// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state. +// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x) +// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader. +// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader. +// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX. +// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix. +// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset. +// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader. +// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader. +// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. +// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. +// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. +// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. +// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. +// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). +// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. +// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. +// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. +// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". +// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. +// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. +// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. +// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. +// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer. +// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". +// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-05-01: OpenGL: Fixed save and restore of current blend func state. +// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) + +//---------------------------------------- +// OpenGL GLSL GLSL +// version version string +//---------------------------------------- +// 2.0 110 "#version 110" +// 2.1 120 "#version 120" +// 3.0 130 "#version 130" +// 3.1 140 "#version 140" +// 3.2 150 "#version 150" +// 3.3 330 "#version 330 core" +// 4.0 400 "#version 400 core" +// 4.1 410 "#version 410 core" +// 4.2 420 "#version 410 core" +// 4.3 430 "#version 430 core" +// ES 2.0 100 "#version 100" = WebGL 1.0 +// ES 3.0 300 "#version 300 es" = WebGL 2.0 +//---------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_opengl3.h" +#include +#include // intptr_t +#if defined(__APPLE__) +#include +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used +#pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#pragma clang diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader) +#endif +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' +#pragma GCC diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader) +#endif + +// GL includes +#if defined(IMGUI_IMPL_OPENGL_ES2) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 2 +#else +#include // Use GL ES 2 +#endif +#if defined(__EMSCRIPTEN__) +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif +#include +#endif +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 3 +#else +#include // Use GL ES 3 +#endif +#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are using our own minimal custom loader based on gl3w. +// In the rest of your app/engine, you can use another loader of your choice (gl3w, glew, glad, glbinding, glext, glLoadGen, etc.). +// If you happen to be developing a new feature for this backend (imgui_impl_opengl3.cpp): +// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. See https://github.com/dearimgui/gl3w_stripped +// - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases +// Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version. +#define IMGL3W_IMPL +#include "imgui_impl_opengl3_loader.h" +#endif + +// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension +#ifndef IMGUI_IMPL_OPENGL_ES2 +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#define glBindVertexArray glBindVertexArrayOES +#define glGenVertexArrays glGenVertexArraysOES +#define glDeleteVertexArrays glDeleteVertexArraysOES +#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES +#endif + +// Desktop GL 2.0+ has glPolygonMode() which GL ES and WebGL don't have. +#ifdef GL_POLYGON_MODE +#define IMGUI_IMPL_HAS_POLYGON_MODE +#endif + +// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#endif + +// Desktop GL 3.3+ and GL ES 3.0+ have glBindSampler() +#if !defined(IMGUI_IMPL_OPENGL_ES2) && (defined(IMGUI_IMPL_OPENGL_ES3) || defined(GL_VERSION_3_3)) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER +#endif + +// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART +#endif + +// Desktop GL use extension detection +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS +#endif + +// [Debugging] +//#define IMGUI_IMPL_OPENGL_DEBUG +#ifdef IMGUI_IMPL_OPENGL_DEBUG +#include +#define GL_CALL(_CALL) do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0) // Call with error check +#else +#define GL_CALL(_CALL) _CALL // Call without error check +#endif + +// OpenGL Data +struct ImGui_ImplOpenGL3_Data +{ + GLuint GlVersion; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) + char GlslVersionString[32]; // Specified by user or detected based on compile time GL settings. + bool GlProfileIsES2; + bool GlProfileIsES3; + bool GlProfileIsCompat; + GLint GlProfileMask; + GLuint FontTexture; + GLuint ShaderHandle; + GLint AttribLocationTex; // Uniforms location + GLint AttribLocationProjMtx; + GLuint AttribLocationVtxPos; // Vertex attributes location + GLuint AttribLocationVtxUV; + GLuint AttribLocationVtxColor; + unsigned int VboHandle, ElementsHandle; + GLsizeiptr VertexBufferSize; + GLsizeiptr IndexBufferSize; + bool HasClipOrigin; + bool UseBufferSubData; + + ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only) +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +struct ImGui_ImplOpenGL3_VtxAttribState +{ + GLint Enabled, Size, Type, Normalized, Stride; + GLvoid* Ptr; + + void GetState(GLint index) + { + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride); + glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr); + } + void SetState(GLint index) + { + glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr); + if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index); + } +}; +#endif + +// Functions +bool ImGui_ImplOpenGL3_Init(const char* glsl_version) +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Initialize our loader +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) + if (imgl3wInit() != 0) + { + fprintf(stderr, "Failed to initialize OpenGL loader!\n"); + return false; + } +#endif + + // Setup backend capabilities flags + ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl3"; + + // Query for GL version (e.g. 320 for GL 3.2) +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GLES 2 + bd->GlVersion = 200; + bd->GlProfileIsES2 = true; +#else + // Desktop or GLES 3 + GLint major = 0; + GLint minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + if (major == 0 && minor == 0) + { + // Query GL_VERSION in desktop GL 2.x, the string will start with "." + const char* gl_version = (const char*)glGetString(GL_VERSION); + sscanf(gl_version, "%d.%d", &major, &minor); + } + bd->GlVersion = (GLuint)(major * 100 + minor * 10); +#if defined(GL_CONTEXT_PROFILE_MASK) + if (bd->GlVersion >= 320) + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask); + bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; +#endif + +#if defined(IMGUI_IMPL_OPENGL_ES3) + bd->GlProfileIsES3 = true; +#endif + + bd->UseBufferSubData = false; + /* + // Query vendor to enable glBufferSubData kludge +#ifdef _WIN32 + if (const char* vendor = (const char*)glGetString(GL_VENDOR)) + if (strncmp(vendor, "Intel", 5) == 0) + bd->UseBufferSubData = true; +#endif + */ +#endif + +#ifdef IMGUI_IMPL_OPENGL_DEBUG + printf("GlVersion = %d\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] +#endif + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. +#endif + + // Store GLSL version string so we can refer to it later in case we recreate shaders. + // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. + if (glsl_version == nullptr) + { +#if defined(IMGUI_IMPL_OPENGL_ES2) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + glsl_version = "#version 300 es"; +#elif defined(__APPLE__) + glsl_version = "#version 150"; +#else + glsl_version = "#version 130"; +#endif + } + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString)); + strcpy(bd->GlslVersionString, glsl_version); + strcat(bd->GlslVersionString, "\n"); + + // Make an arbitrary GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know! + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + // Detect extensions we support + bd->HasClipOrigin = (bd->GlVersion >= 450); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS + GLint num_extensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + for (GLint i = 0; i < num_extensions; i++) + { + const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i); + if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0) + bd->HasClipOrigin = true; + } +#endif + + return true; +} + +void ImGui_ImplOpenGL3_Shutdown() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplOpenGL3_DestroyDeviceObjects(); + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + IM_DELETE(bd); +} + +void ImGui_ImplOpenGL3_NewFrame() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?"); + + if (!bd->ShaderHandle) + ImGui_ImplOpenGL3_CreateDeviceObjects(); +} + +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glEnable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (bd->GlVersion >= 310) + glDisable(GL_PRIMITIVE_RESTART); +#endif +#ifdef IMGUI_IMPL_HAS_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + + // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) +#if defined(GL_CLIP_ORIGIN) + bool clip_origin_lower_left = true; + if (bd->HasClipOrigin) + { + GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); + if (current_clip_origin == GL_UPPER_LEFT) + clip_origin_lower_left = false; + } +#endif + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height)); + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; +#if defined(GL_CLIP_ORIGIN) + if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left +#endif + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, + }; + glUseProgram(bd->ShaderHandle); + glUniform1i(bd->AttribLocationTex, 0); + glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->GlVersion >= 330 || bd->GlProfileIsES3) + glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. +#endif + + (void)vertex_array_object; +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(vertex_array_object); +#endif + + // Bind vertex/index buffers and setup attributes for ImDrawVert + GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle)); + GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor)); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos))); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv))); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col))); +} + +// OpenGL3 Render function. +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. +// This is in order to be able to run within an OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) + return; + + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program); + GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; } +#endif + GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+. + GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor); +#endif +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object); +#endif +#ifdef IMGUI_IMPL_HAS_POLYGON_MODE + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); +#endif + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; +#endif + + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + GLuint vertex_array_object = 0; +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GL_CALL(glGenVertexArrays(1, &vertex_array_object)); +#endif + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + // Upload vertex/index buffers + // - OpenGL drivers are in a very sorry state nowadays.... + // During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports + // of leaks on Intel GPU when using multi-viewports on Windows. + // - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel. + // - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code. + // We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path. + // - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues. + const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert); + const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx); + if (bd->UseBufferSubData) + { + if (bd->VertexBufferSize < vtx_buffer_size) + { + bd->VertexBufferSize = vtx_buffer_size; + GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW)); + } + if (bd->IndexBufferSize < idx_buffer_size) + { + bd->IndexBufferSize = idx_buffer_size; + GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW)); + } + GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data)); + GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data)); + } + else + { + GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW)); + GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW)); + } + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle (Y is inverted in OpenGL) + GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y))); + + // Bind texture, Draw + GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset)); + else +#endif + GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)))); + } + } + } + + // Destroy the temporary VAO +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GL_CALL(glDeleteVertexArrays(1, &vertex_array_object)); +#endif + + // Restore modified GL state + // This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220. + if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->GlVersion >= 330 || bd->GlProfileIsES3) + glBindSampler(0, last_sampler); +#endif + glActiveTexture(last_active_texture); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(last_vertex_array_object); +#endif + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos); + last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV); + last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor); +#endif + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } +#endif + +#ifdef IMGUI_IMPL_HAS_POLYGON_MODE + // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons + if (bd->GlVersion <= 310 || bd->GlProfileIsCompat) + { + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); + glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + } + else + { + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + } +#endif // IMGUI_IMPL_HAS_POLYGON_MODE + + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); + (void)bd; // Not all compilation paths use this +} + +bool ImGui_ImplOpenGL3_CreateFontsTexture() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Build texture atlas + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + GL_CALL(glGenTextures(1, &bd->FontTexture)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); +#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#endif + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); + + // Restore state + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); + + return true; +} + +void ImGui_ImplOpenGL3_DestroyFontsTexture() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->FontTexture) + { + glDeleteTextures(1, &bd->FontTexture); + io.Fonts->SetTexID(0); + bd->FontTexture = 0; + } +} + +// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. +static bool CheckShader(GLuint handle, const char* desc) +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLint status = 0, log_length = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +// If you get an error please report on GitHub. You may try different GL context version or GLSL version. +static bool CheckProgram(GLuint handle, const char* desc) +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLint status = 0, log_length = 0; + glGetProgramiv(handle, GL_LINK_STATUS, &status); + glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +bool ImGui_ImplOpenGL3_CreateDeviceObjects() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Backup GL state + GLint last_texture, last_array_buffer; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GLint last_vertex_array; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); +#endif + + // Parse GLSL version string + int glsl_version = 130; + sscanf(bd->GlslVersionString, "#version %d", &glsl_version); + + const GLchar* vertex_shader_glsl_120 = + "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_130 = + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_300_es = + "precision highp float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_410_core = + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_120 = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D Texture;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_130 = + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_300_es = + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_410_core = + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "uniform sampler2D Texture;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + // Select shaders matching our GLSL versions + const GLchar* vertex_shader = nullptr; + const GLchar* fragment_shader = nullptr; + if (glsl_version < 130) + { + vertex_shader = vertex_shader_glsl_120; + fragment_shader = fragment_shader_glsl_120; + } + else if (glsl_version >= 410) + { + vertex_shader = vertex_shader_glsl_410_core; + fragment_shader = fragment_shader_glsl_410_core; + } + else if (glsl_version == 300) + { + vertex_shader = vertex_shader_glsl_300_es; + fragment_shader = fragment_shader_glsl_300_es; + } + else + { + vertex_shader = vertex_shader_glsl_130; + fragment_shader = fragment_shader_glsl_130; + } + + // Create shaders + const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader }; + GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr); + glCompileShader(vert_handle); + CheckShader(vert_handle, "vertex shader"); + + const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader }; + GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr); + glCompileShader(frag_handle); + CheckShader(frag_handle, "fragment shader"); + + // Link + bd->ShaderHandle = glCreateProgram(); + glAttachShader(bd->ShaderHandle, vert_handle); + glAttachShader(bd->ShaderHandle, frag_handle); + glLinkProgram(bd->ShaderHandle); + CheckProgram(bd->ShaderHandle, "shader program"); + + glDetachShader(bd->ShaderHandle, vert_handle); + glDetachShader(bd->ShaderHandle, frag_handle); + glDeleteShader(vert_handle); + glDeleteShader(frag_handle); + + bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture"); + bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx"); + bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position"); + bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV"); + bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color"); + + // Create buffers + glGenBuffers(1, &bd->VboHandle); + glGenBuffers(1, &bd->ElementsHandle); + + ImGui_ImplOpenGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(last_vertex_array); +#endif + + return true; +} + +void ImGui_ImplOpenGL3_DestroyDeviceObjects() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } + if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } + if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } + ImGui_ImplOpenGL3_DestroyFontsTexture(); +} + +//----------------------------------------------------------------------------- + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.h b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.h new file mode 100644 index 00000000..23eb9247 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3.h @@ -0,0 +1,66 @@ +// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! +// [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). + +// About WebGL/ES: +// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. +// - This is done automatically on iOS, Android and Emscripten targets. +// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// About GLSL version: +// The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string. +// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" +// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +// Backend API +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); + +// (Optional) Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); + +// Specific OpenGL ES versions +//#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten +//#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android + +// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. +#if !defined(IMGUI_IMPL_OPENGL_ES2) \ + && !defined(IMGUI_IMPL_OPENGL_ES3) + +// Try to detect GLES on matching platforms +#if defined(__APPLE__) +#include +#endif +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) +#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" +#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__) +#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" +#else +// Otherwise imgui_impl_opengl3_loader.h will be used. +#endif + +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3_loader.h b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3_loader.h new file mode 100644 index 00000000..15ba44f5 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_opengl3_loader.h @@ -0,0 +1,810 @@ +//----------------------------------------------------------------------------- +// About imgui_impl_opengl3_loader.h: +// +// We embed our own OpenGL loader to not require user to provide their own or to have to use ours, +// which proved to be endless problems for users. +// Our loader is custom-generated, based on gl3w but automatically filtered to only include +// enums/functions that we use in our imgui_impl_opengl3.cpp source file in order to be small. +// +// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY imgui_impl_opengl3.cpp ONLY. +// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE. +// +// IF YOU GET BUILD ERRORS IN THIS FILE (commonly macro redefinitions or function redefinitions): +// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCUDING 'imgui_impl_opengl3_loader.h' +// IN THE SAME COMPILATION UNIT AS ONE OF YOUR FILE WHICH IS USING A THIRD-PARTY OPENGL LOADER. +// (e.g. COULD HAPPEN IF YOU ARE DOING A UNITY/JUMBO BUILD, OR INCLUDING .CPP FILES FROM OTHERS) +// YOU SHOULD NOT BUILD BOTH IN THE SAME COMPILATION UNIT. +// BUT IF YOU REALLY WANT TO, you can '#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM' and imgui_impl_opengl3.cpp +// WILL NOT BE USING OUR LOADER, AND INSTEAD EXPECT ANOTHER/YOUR LOADER TO BE AVAILABLE IN THE COMPILATION UNIT. +// +// Regenerate with: +// python gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt +// +// More info: +// https://github.com/dearimgui/gl3w_stripped +// https://github.com/ocornut/imgui/issues/4445 +//----------------------------------------------------------------------------- + +/* + * This file was generated with gl3w_gen.py, part of imgl3w + * (hosted at https://github.com/dearimgui/gl3w_stripped) + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __gl3w_h_ +#define __gl3w_h_ + +// Adapted from KHR/khrplatform.h to avoid including entire file. +#ifndef __khrplatform_h_ +typedef float khronos_float_t; +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef signed long long int khronos_ssize_t; +#else +typedef signed long int khronos_intptr_t; +typedef signed long int khronos_ssize_t; +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +typedef signed __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) +#include +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#else +typedef signed long long khronos_int64_t; +typedef unsigned long long khronos_uint64_t; +#endif +#endif // __khrplatform_h_ + +#ifndef __gl_glcorearb_h_ +#define __gl_glcorearb_h_ 1 +#ifdef __cplusplus +extern "C" { +#endif +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif +/* glcorearb.h is for use with OpenGL core profile implementations. +** It should should be placed in the same directory as gl.h and +** included as . +** +** glcorearb.h includes only APIs in the latest OpenGL core profile +** implementation together with APIs in newer ARB extensions which +** can be supported by the core profile. It does not, and never will +** include functionality removed from the core profile, such as +** fixed-function vertex and fragment processing. +** +** Do not #include both and either of or +** in the same source file. +*/ +/* Generated C header for: + * API: gl + * Profile: core + * Versions considered: .* + * Versions emitted: .* + * Default extensions included: glcore + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ +#ifndef GL_VERSION_1_0 +typedef void GLvoid; +typedef unsigned int GLenum; + +typedef khronos_float_t GLfloat; +typedef int GLint; +typedef int GLsizei; +typedef unsigned int GLbitfield; +typedef double GLdouble; +typedef unsigned int GLuint; +typedef unsigned char GLboolean; +typedef khronos_uint8_t GLubyte; +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 +#define GL_ONE 1 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_POLYGON_MODE 0x0B40 +#define GL_CULL_FACE 0x0B44 +#define GL_DEPTH_TEST 0x0B71 +#define GL_STENCIL_TEST 0x0B90 +#define GL_VIEWPORT 0x0BA2 +#define GL_BLEND 0x0BE2 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_RGBA 0x1908 +#define GL_FILL 0x1B02 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_LINEAR 0x2601 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +typedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLFLUSHPROC) (void); +typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void); +typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name); +typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode); +GLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glClear (GLbitfield mask); +GLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glDisable (GLenum cap); +GLAPI void APIENTRY glEnable (GLenum cap); +GLAPI void APIENTRY glFlush (void); +GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param); +GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GLAPI GLenum APIENTRY glGetError (void); +GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data); +GLAPI const GLubyte *APIENTRY glGetString (GLenum name); +GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap); +GLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_VERSION_1_0 */ +#ifndef GL_VERSION_1_1 +typedef khronos_float_t GLclampf; +typedef double GLclampd; +#define GL_TEXTURE_BINDING_2D 0x8069 +typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures); +#endif +#endif /* GL_VERSION_1_1 */ +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_ACTIVE_TEXTURE 0x84E0 +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +#endif +#endif /* GL_VERSION_1_3 */ +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_FUNC_ADD 0x8006 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +#endif +#endif /* GL_VERSION_1_4 */ +#ifndef GL_VERSION_1_5 +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_STREAM_DRAW 0x88E0 +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +#endif +#endif /* GL_VERSION_1_5 */ +#ifndef GL_VERSION_2_0 +typedef char GLchar; +typedef khronos_int16_t GLshort; +typedef khronos_int8_t GLbyte; +typedef khronos_uint16_t GLushort; +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_UPPER_LEFT 0x8CA2 +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#endif +#endif /* GL_VERSION_2_0 */ +#ifndef GL_VERSION_3_0 +typedef khronos_uint16_t GLhalf; +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index); +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +#endif +#endif /* GL_VERSION_3_0 */ +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +#define GL_PRIMITIVE_RESTART 0x8F9D +#endif /* GL_VERSION_3_1 */ +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +typedef struct __GLsync *GLsync; +typedef khronos_uint64_t GLuint64; +typedef khronos_int64_t GLint64; +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +#endif +#endif /* GL_VERSION_3_2 */ +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +#define GL_SAMPLER_BINDING 0x8919 +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +#endif +#endif /* GL_VERSION_3_3 */ +#ifndef GL_VERSION_4_1 +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#endif /* GL_VERSION_4_1 */ +#ifndef GL_VERSION_4_3 +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#endif /* GL_VERSION_4_3 */ +#ifndef GL_VERSION_4_5 +#define GL_CLIP_ORIGIN 0x935C +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); +#endif /* GL_VERSION_4_5 */ +#ifndef GL_ARB_bindless_texture +typedef khronos_uint64_t GLuint64EXT; +#endif /* GL_ARB_bindless_texture */ +#ifndef GL_ARB_cl_event +struct _cl_context; +struct _cl_event; +#endif /* GL_ARB_cl_event */ +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +#endif /* GL_ARB_clip_control */ +#ifndef GL_ARB_debug_output +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#endif /* GL_ARB_debug_output */ +#ifndef GL_EXT_EGL_image_storage +typedef void *GLeglImageOES; +#endif /* GL_EXT_EGL_image_storage */ +#ifndef GL_EXT_direct_state_access +typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param); +#endif /* GL_EXT_direct_state_access */ +#ifndef GL_NV_draw_vulkan_image +typedef void (APIENTRY *GLVULKANPROCNV)(void); +#endif /* GL_NV_draw_vulkan_image */ +#ifndef GL_NV_gpu_shader5 +typedef khronos_int64_t GLint64EXT; +#endif /* GL_NV_gpu_shader5 */ +#ifndef GL_NV_vertex_buffer_unified_memory +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#endif /* GL_NV_vertex_buffer_unified_memory */ +#ifdef __cplusplus +} +#endif +#endif + +#ifndef GL3W_API +#define GL3W_API +#endif + +#ifndef __gl_h_ +#define __gl_h_ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define GL3W_OK 0 +#define GL3W_ERROR_INIT -1 +#define GL3W_ERROR_LIBRARY_OPEN -2 +#define GL3W_ERROR_OPENGL_VERSION -3 + +typedef void (*GL3WglProc)(void); +typedef GL3WglProc (*GL3WGetProcAddressProc)(const char *proc); + +/* gl3w api */ +GL3W_API int imgl3wInit(void); +GL3W_API int imgl3wInit2(GL3WGetProcAddressProc proc); +GL3W_API int imgl3wIsSupported(int major, int minor); +GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc); + +/* gl3w internal state */ +union ImGL3WProcs { + GL3WglProc ptr[59]; + struct { + PFNGLACTIVETEXTUREPROC ActiveTexture; + PFNGLATTACHSHADERPROC AttachShader; + PFNGLBINDBUFFERPROC BindBuffer; + PFNGLBINDSAMPLERPROC BindSampler; + PFNGLBINDTEXTUREPROC BindTexture; + PFNGLBINDVERTEXARRAYPROC BindVertexArray; + PFNGLBLENDEQUATIONPROC BlendEquation; + PFNGLBLENDEQUATIONSEPARATEPROC BlendEquationSeparate; + PFNGLBLENDFUNCSEPARATEPROC BlendFuncSeparate; + PFNGLBUFFERDATAPROC BufferData; + PFNGLBUFFERSUBDATAPROC BufferSubData; + PFNGLCLEARPROC Clear; + PFNGLCLEARCOLORPROC ClearColor; + PFNGLCOMPILESHADERPROC CompileShader; + PFNGLCREATEPROGRAMPROC CreateProgram; + PFNGLCREATESHADERPROC CreateShader; + PFNGLDELETEBUFFERSPROC DeleteBuffers; + PFNGLDELETEPROGRAMPROC DeleteProgram; + PFNGLDELETESHADERPROC DeleteShader; + PFNGLDELETETEXTURESPROC DeleteTextures; + PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays; + PFNGLDETACHSHADERPROC DetachShader; + PFNGLDISABLEPROC Disable; + PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray; + PFNGLDRAWELEMENTSPROC DrawElements; + PFNGLDRAWELEMENTSBASEVERTEXPROC DrawElementsBaseVertex; + PFNGLENABLEPROC Enable; + PFNGLENABLEVERTEXATTRIBARRAYPROC EnableVertexAttribArray; + PFNGLFLUSHPROC Flush; + PFNGLGENBUFFERSPROC GenBuffers; + PFNGLGENTEXTURESPROC GenTextures; + PFNGLGENVERTEXARRAYSPROC GenVertexArrays; + PFNGLGETATTRIBLOCATIONPROC GetAttribLocation; + PFNGLGETERRORPROC GetError; + PFNGLGETINTEGERVPROC GetIntegerv; + PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog; + PFNGLGETPROGRAMIVPROC GetProgramiv; + PFNGLGETSHADERINFOLOGPROC GetShaderInfoLog; + PFNGLGETSHADERIVPROC GetShaderiv; + PFNGLGETSTRINGPROC GetString; + PFNGLGETSTRINGIPROC GetStringi; + PFNGLGETUNIFORMLOCATIONPROC GetUniformLocation; + PFNGLGETVERTEXATTRIBPOINTERVPROC GetVertexAttribPointerv; + PFNGLGETVERTEXATTRIBIVPROC GetVertexAttribiv; + PFNGLISENABLEDPROC IsEnabled; + PFNGLISPROGRAMPROC IsProgram; + PFNGLLINKPROGRAMPROC LinkProgram; + PFNGLPIXELSTOREIPROC PixelStorei; + PFNGLPOLYGONMODEPROC PolygonMode; + PFNGLREADPIXELSPROC ReadPixels; + PFNGLSCISSORPROC Scissor; + PFNGLSHADERSOURCEPROC ShaderSource; + PFNGLTEXIMAGE2DPROC TexImage2D; + PFNGLTEXPARAMETERIPROC TexParameteri; + PFNGLUNIFORM1IPROC Uniform1i; + PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv; + PFNGLUSEPROGRAMPROC UseProgram; + PFNGLVERTEXATTRIBPOINTERPROC VertexAttribPointer; + PFNGLVIEWPORTPROC Viewport; + } gl; +}; + +GL3W_API extern union ImGL3WProcs imgl3wProcs; + +/* OpenGL functions */ +#define glActiveTexture imgl3wProcs.gl.ActiveTexture +#define glAttachShader imgl3wProcs.gl.AttachShader +#define glBindBuffer imgl3wProcs.gl.BindBuffer +#define glBindSampler imgl3wProcs.gl.BindSampler +#define glBindTexture imgl3wProcs.gl.BindTexture +#define glBindVertexArray imgl3wProcs.gl.BindVertexArray +#define glBlendEquation imgl3wProcs.gl.BlendEquation +#define glBlendEquationSeparate imgl3wProcs.gl.BlendEquationSeparate +#define glBlendFuncSeparate imgl3wProcs.gl.BlendFuncSeparate +#define glBufferData imgl3wProcs.gl.BufferData +#define glBufferSubData imgl3wProcs.gl.BufferSubData +#define glClear imgl3wProcs.gl.Clear +#define glClearColor imgl3wProcs.gl.ClearColor +#define glCompileShader imgl3wProcs.gl.CompileShader +#define glCreateProgram imgl3wProcs.gl.CreateProgram +#define glCreateShader imgl3wProcs.gl.CreateShader +#define glDeleteBuffers imgl3wProcs.gl.DeleteBuffers +#define glDeleteProgram imgl3wProcs.gl.DeleteProgram +#define glDeleteShader imgl3wProcs.gl.DeleteShader +#define glDeleteTextures imgl3wProcs.gl.DeleteTextures +#define glDeleteVertexArrays imgl3wProcs.gl.DeleteVertexArrays +#define glDetachShader imgl3wProcs.gl.DetachShader +#define glDisable imgl3wProcs.gl.Disable +#define glDisableVertexAttribArray imgl3wProcs.gl.DisableVertexAttribArray +#define glDrawElements imgl3wProcs.gl.DrawElements +#define glDrawElementsBaseVertex imgl3wProcs.gl.DrawElementsBaseVertex +#define glEnable imgl3wProcs.gl.Enable +#define glEnableVertexAttribArray imgl3wProcs.gl.EnableVertexAttribArray +#define glFlush imgl3wProcs.gl.Flush +#define glGenBuffers imgl3wProcs.gl.GenBuffers +#define glGenTextures imgl3wProcs.gl.GenTextures +#define glGenVertexArrays imgl3wProcs.gl.GenVertexArrays +#define glGetAttribLocation imgl3wProcs.gl.GetAttribLocation +#define glGetError imgl3wProcs.gl.GetError +#define glGetIntegerv imgl3wProcs.gl.GetIntegerv +#define glGetProgramInfoLog imgl3wProcs.gl.GetProgramInfoLog +#define glGetProgramiv imgl3wProcs.gl.GetProgramiv +#define glGetShaderInfoLog imgl3wProcs.gl.GetShaderInfoLog +#define glGetShaderiv imgl3wProcs.gl.GetShaderiv +#define glGetString imgl3wProcs.gl.GetString +#define glGetStringi imgl3wProcs.gl.GetStringi +#define glGetUniformLocation imgl3wProcs.gl.GetUniformLocation +#define glGetVertexAttribPointerv imgl3wProcs.gl.GetVertexAttribPointerv +#define glGetVertexAttribiv imgl3wProcs.gl.GetVertexAttribiv +#define glIsEnabled imgl3wProcs.gl.IsEnabled +#define glIsProgram imgl3wProcs.gl.IsProgram +#define glLinkProgram imgl3wProcs.gl.LinkProgram +#define glPixelStorei imgl3wProcs.gl.PixelStorei +#define glPolygonMode imgl3wProcs.gl.PolygonMode +#define glReadPixels imgl3wProcs.gl.ReadPixels +#define glScissor imgl3wProcs.gl.Scissor +#define glShaderSource imgl3wProcs.gl.ShaderSource +#define glTexImage2D imgl3wProcs.gl.TexImage2D +#define glTexParameteri imgl3wProcs.gl.TexParameteri +#define glUniform1i imgl3wProcs.gl.Uniform1i +#define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv +#define glUseProgram imgl3wProcs.gl.UseProgram +#define glVertexAttribPointer imgl3wProcs.gl.VertexAttribPointer +#define glViewport imgl3wProcs.gl.Viewport + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef IMGL3W_IMPL +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define GL3W_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +static HMODULE libgl; +typedef PROC(__stdcall* GL3WglGetProcAddr)(LPCSTR); +static GL3WglGetProcAddr wgl_get_proc_address; + +static int open_libgl(void) +{ + libgl = LoadLibraryA("opengl32.dll"); + if (!libgl) + return GL3W_ERROR_LIBRARY_OPEN; + wgl_get_proc_address = (GL3WglGetProcAddr)GetProcAddress(libgl, "wglGetProcAddress"); + return GL3W_OK; +} + +static void close_libgl(void) { FreeLibrary(libgl); } +static GL3WglProc get_proc(const char *proc) +{ + GL3WglProc res; + res = (GL3WglProc)wgl_get_proc_address(proc); + if (!res) + res = (GL3WglProc)GetProcAddress(libgl, proc); + return res; +} +#elif defined(__APPLE__) +#include + +static void *libgl; +static int open_libgl(void) +{ + libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + return GL3W_ERROR_LIBRARY_OPEN; + return GL3W_OK; +} + +static void close_libgl(void) { dlclose(libgl); } + +static GL3WglProc get_proc(const char *proc) +{ + GL3WglProc res; + *(void **)(&res) = dlsym(libgl, proc); + return res; +} +#else +#include + +static void *libgl; +static GL3WglProc (*glx_get_proc_address)(const GLubyte *); + +static int open_libgl(void) +{ + // While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983 + libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + return GL3W_ERROR_LIBRARY_OPEN; + *(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB"); + return GL3W_OK; +} + +static void close_libgl(void) { dlclose(libgl); } + +static GL3WglProc get_proc(const char *proc) +{ + GL3WglProc res; + res = glx_get_proc_address((const GLubyte *)proc); + if (!res) + *(void **)(&res) = dlsym(libgl, proc); + return res; +} +#endif + +static struct { int major, minor; } version; + +static int parse_version(void) +{ + if (!glGetIntegerv) + return GL3W_ERROR_INIT; + glGetIntegerv(GL_MAJOR_VERSION, &version.major); + glGetIntegerv(GL_MINOR_VERSION, &version.minor); + if (version.major == 0 && version.minor == 0) + { + // Query GL_VERSION in desktop GL 2.x, the string will start with "." + if (const char* gl_version = (const char*)glGetString(GL_VERSION)) + sscanf(gl_version, "%d.%d", &version.major, &version.minor); + } + if (version.major < 2) + return GL3W_ERROR_OPENGL_VERSION; + return GL3W_OK; +} + +static void load_procs(GL3WGetProcAddressProc proc); + +int imgl3wInit(void) +{ + int res = open_libgl(); + if (res) + return res; + atexit(close_libgl); + return imgl3wInit2(get_proc); +} + +int imgl3wInit2(GL3WGetProcAddressProc proc) +{ + load_procs(proc); + return parse_version(); +} + +int imgl3wIsSupported(int major, int minor) +{ + if (major < 2) + return 0; + if (version.major == major) + return version.minor >= minor; + return version.major >= major; +} + +GL3WglProc imgl3wGetProcAddress(const char *proc) { return get_proc(proc); } + +static const char *proc_names[] = { + "glActiveTexture", + "glAttachShader", + "glBindBuffer", + "glBindSampler", + "glBindTexture", + "glBindVertexArray", + "glBlendEquation", + "glBlendEquationSeparate", + "glBlendFuncSeparate", + "glBufferData", + "glBufferSubData", + "glClear", + "glClearColor", + "glCompileShader", + "glCreateProgram", + "glCreateShader", + "glDeleteBuffers", + "glDeleteProgram", + "glDeleteShader", + "glDeleteTextures", + "glDeleteVertexArrays", + "glDetachShader", + "glDisable", + "glDisableVertexAttribArray", + "glDrawElements", + "glDrawElementsBaseVertex", + "glEnable", + "glEnableVertexAttribArray", + "glFlush", + "glGenBuffers", + "glGenTextures", + "glGenVertexArrays", + "glGetAttribLocation", + "glGetError", + "glGetIntegerv", + "glGetProgramInfoLog", + "glGetProgramiv", + "glGetShaderInfoLog", + "glGetShaderiv", + "glGetString", + "glGetStringi", + "glGetUniformLocation", + "glGetVertexAttribPointerv", + "glGetVertexAttribiv", + "glIsEnabled", + "glIsProgram", + "glLinkProgram", + "glPixelStorei", + "glPolygonMode", + "glReadPixels", + "glScissor", + "glShaderSource", + "glTexImage2D", + "glTexParameteri", + "glUniform1i", + "glUniformMatrix4fv", + "glUseProgram", + "glVertexAttribPointer", + "glViewport", +}; + +GL3W_API union ImGL3WProcs imgl3wProcs; + +static void load_procs(GL3WGetProcAddressProc proc) +{ + size_t i; + for (i = 0; i < GL3W_ARRAY_SIZE(proc_names); i++) + imgl3wProcs.ptr[i] = proc(proc_names[i]); +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.cpp new file mode 100644 index 00000000..f6ccb3e3 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.cpp @@ -0,0 +1,462 @@ +// dear imgui: Platform Binding for Windows (standard windows API for 32 and 64 bits applications) +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) + +// Implemented features: +// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE). +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. + +#include "imgui.h" +#include "imgui_impl_win32.h" +#include "imgui_extra_keys.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +// Using XInput library for gamepad (with recent Windows SDK this may leads to executables which won't run on Windows 7) +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +#include +#else +#define IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT +#endif +#if defined(_MSC_VER) && !defined(IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT) +#pragma comment(lib, "xinput") +//#pragma comment(lib, "Xinput9_1_0") +#endif + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2020-03-03: Inputs: Calling AddInputCharacterUTF16() to support surrogate pairs leading to codepoint >= 0x10000 (for more complete CJK inputs) +// 2020-02-17: Added ImGui_ImplWin32_EnableDpiAwareness(), ImGui_ImplWin32_GetDpiScaleForHwnd(), ImGui_ImplWin32_GetDpiScaleForMonitor() helper functions. +// 2020-01-14: Inputs: Added support for #define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD/IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT. +// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. +// 2019-05-11: Inputs: Don't filter value from WM_CHAR before calling AddInputCharacter(). +// 2019-01-17: Misc: Using GetForegroundWindow()+IsChild() instead of GetActiveWindow() to be compatible with windows created in a different thread or parent. +// 2019-01-17: Inputs: Added support for mouse buttons 4 and 5 via WM_XBUTTON* messages. +// 2019-01-15: Inputs: Added support for XInput gamepads (if ImGuiConfigFlags_NavEnableGamepad is set by user application). +// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. +// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. +// 2018-06-10: Inputs: Fixed handling of mouse wheel messages to support fine position messages (typically sent by track-pads). +// 2018-06-08: Misc: Extracted imgui_impl_win32.cpp/.h away from the old combined DX9/DX10/DX11/DX12 examples. +// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos flags + honor ImGuiConfigFlags_NoMouseCursorChange flag. +// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). +// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. +// 2018-02-06: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set). +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. +// 2018-01-08: Inputs: Added mapping for ImGuiKey_Insert. +// 2018-01-05: Inputs: Added WM_LBUTTONDBLCLK double-click handlers for window classes with the CS_DBLCLKS flag. +// 2017-10-23: Inputs: Added WM_SYSKEYDOWN / WM_SYSKEYUP handlers so e.g. the VK_MENU key can be read. +// 2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging. +// 2016-11-12: Inputs: Only call Win32 ::SetCursor(NULL) when io.MouseDrawCursor is set. + +// Win32 Data +static HWND g_hWnd = NULL; +static INT64 g_Time = 0; +static INT64 g_TicksPerSecond = 0; +static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT; +static bool g_HasGamepad = false; +static bool g_WantUpdateHasGamepad = true; + +// Functions +bool ImGui_ImplWin32_Init(void* hwnd) +{ + if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&g_TicksPerSecond)) + return false; + if (!::QueryPerformanceCounter((LARGE_INTEGER*)&g_Time)) + return false; + + // Setup back-end capabilities flags + g_hWnd = (HWND)hwnd; + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + io.BackendPlatformName = "imgui_impl_win32"; + //io.ImeWindowHandle = hwnd; + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. + io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = VK_UP; + io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; + io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR; + io.KeyMap[ImGuiKey_PageDown] = VK_NEXT; + io.KeyMap[ImGuiKey_Home] = VK_HOME; + io.KeyMap[ImGuiKey_End] = VK_END; + io.KeyMap[ImGuiKey_Insert] = VK_INSERT; + io.KeyMap[ImGuiKey_Delete] = VK_DELETE; + io.KeyMap[ImGuiKey_Backspace] = VK_BACK; + io.KeyMap[ImGuiKey_Space] = VK_SPACE; + io.KeyMap[ImGuiKey_Enter] = VK_RETURN; + io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE; +# if defined(IMGUI_VERSION_NUM) && (IMGUI_VERSION_NUM >= 18604) + io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN; +# else + io.KeyMap[ImGuiKey_KeyPadEnter] = VK_RETURN; +# endif + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; + + int f_index = GetEnumValueForF(); + int d_index = GetEnumValueForD(); + if (f_index >= 0) + io.KeyMap[f_index] = 'F'; + if (d_index >= 0) + io.KeyMap[d_index] = 'D'; + + return true; +} + +void ImGui_ImplWin32_Shutdown() +{ + g_hWnd = (HWND)0; +} + +static bool ImGui_ImplWin32_UpdateMouseCursor() +{ + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return false; + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) + { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + ::SetCursor(NULL); + } + else + { + // Show OS mouse cursor + LPTSTR win32_cursor = IDC_ARROW; + switch (imgui_cursor) + { + case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break; + case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break; + case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break; + case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break; + case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break; + case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break; + case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break; + case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break; + case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break; + } + ::SetCursor(::LoadCursor(NULL, win32_cursor)); + } + return true; +} + +static void ImGui_ImplWin32_UpdateMousePos() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + { + POINT pos = { (int)(io.MousePos.x), (int)(io.MousePos.y) }; + ::ClientToScreen(g_hWnd, &pos); + ::SetCursorPos(pos.x, pos.y); + } + + // Set mouse position + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + POINT pos; + if (HWND active_window = ::GetForegroundWindow()) + if (active_window == g_hWnd || ::IsChild(active_window, g_hWnd)) + if (::GetCursorPos(&pos) && ::ScreenToClient(g_hWnd, &pos)) + io.MousePos = ImVec2((float)pos.x, (float)pos.y); +} + +// Gamepad navigation mapping +static void ImGui_ImplWin32_UpdateGamepads() +{ +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + ImGuiIO& io = ImGui::GetIO(); + memset(io.NavInputs, 0, sizeof(io.NavInputs)); + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + + // Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow. + // Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE. + if (g_WantUpdateHasGamepad) + { + XINPUT_CAPABILITIES caps; + g_HasGamepad = (XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS); + g_WantUpdateHasGamepad = false; + } + + XINPUT_STATE xinput_state; + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + if (g_HasGamepad && XInputGetState(0, &xinput_state) == ERROR_SUCCESS) + { + const XINPUT_GAMEPAD& gamepad = xinput_state.Gamepad; + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + #define MAP_BUTTON(NAV_NO, BUTTON_ENUM) { io.NavInputs[NAV_NO] = (gamepad.wButtons & BUTTON_ENUM) ? 1.0f : 0.0f; } + #define MAP_ANALOG(NAV_NO, VALUE, V0, V1) { float vn = (float)(VALUE - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; } + MAP_BUTTON(ImGuiNavInput_Activate, XINPUT_GAMEPAD_A); // Cross / A + MAP_BUTTON(ImGuiNavInput_Cancel, XINPUT_GAMEPAD_B); // Circle / B + MAP_BUTTON(ImGuiNavInput_Menu, XINPUT_GAMEPAD_X); // Square / X + MAP_BUTTON(ImGuiNavInput_Input, XINPUT_GAMEPAD_Y); // Triangle / Y + MAP_BUTTON(ImGuiNavInput_DpadLeft, XINPUT_GAMEPAD_DPAD_LEFT); // D-Pad Left + MAP_BUTTON(ImGuiNavInput_DpadRight, XINPUT_GAMEPAD_DPAD_RIGHT); // D-Pad Right + MAP_BUTTON(ImGuiNavInput_DpadUp, XINPUT_GAMEPAD_DPAD_UP); // D-Pad Up + MAP_BUTTON(ImGuiNavInput_DpadDown, XINPUT_GAMEPAD_DPAD_DOWN); // D-Pad Down + MAP_BUTTON(ImGuiNavInput_FocusPrev, XINPUT_GAMEPAD_LEFT_SHOULDER); // L1 / LB + MAP_BUTTON(ImGuiNavInput_FocusNext, XINPUT_GAMEPAD_RIGHT_SHOULDER); // R1 / RB + MAP_BUTTON(ImGuiNavInput_TweakSlow, XINPUT_GAMEPAD_LEFT_SHOULDER); // L1 / LB + MAP_BUTTON(ImGuiNavInput_TweakFast, XINPUT_GAMEPAD_RIGHT_SHOULDER); // R1 / RB + MAP_ANALOG(ImGuiNavInput_LStickLeft, gamepad.sThumbLX, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); + MAP_ANALOG(ImGuiNavInput_LStickRight, gamepad.sThumbLX, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiNavInput_LStickUp, gamepad.sThumbLY, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767); + MAP_ANALOG(ImGuiNavInput_LStickDown, gamepad.sThumbLY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32767); + #undef MAP_BUTTON + #undef MAP_ANALOG + } +#endif // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +} + +void ImGui_ImplWin32_NewFrame() +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + + // Setup display size (every frame to accommodate for window resizing) + RECT rect; + ::GetClientRect(g_hWnd, &rect); + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); + + // Setup time step + INT64 current_time; + ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time); + io.DeltaTime = (float)(current_time - g_Time) / g_TicksPerSecond; + g_Time = current_time; + + // Read keyboard modifiers inputs + io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0; + io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0; + io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0; + io.KeySuper = false; + // io.KeysDown[], io.MousePos, io.MouseDown[], io.MouseWheel: filled by the WndProc handler below. + + // Update OS mouse position + ImGui_ImplWin32_UpdateMousePos(); + + // Update OS mouse cursor with the cursor requested by imgui + ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor(); + if (g_LastMouseCursor != mouse_cursor) + { + g_LastMouseCursor = mouse_cursor; + ImGui_ImplWin32_UpdateMouseCursor(); + } + + // Update game controllers (if enabled and available) + ImGui_ImplWin32_UpdateGamepads(); +} + +// Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions. +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef DBT_DEVNODES_CHANGED +#define DBT_DEVNODES_CHANGED 0x0007 +#endif + +// Win32 message handler (process Win32 mouse/keyboard inputs, etc.) +// Call from your application's message handler. +// When implementing your own back-end, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. +// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags. +// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds. +// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag. +#if 0 +// Copy this line into your .cpp file to forward declare the function. +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +#endif +IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (ImGui::GetCurrentContext() == NULL) + return 0; + + ImGuiIO& io = ImGui::GetIO(); + switch (msg) + { + case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: + { + int button = 0; + if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; } + if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; } + if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; } + if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } + if (!ImGui::IsAnyMouseDown() && ::GetCapture() == NULL) + ::SetCapture(hwnd); + io.MouseDown[button] = true; + return 0; + } + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + { + int button = 0; + if (msg == WM_LBUTTONUP) { button = 0; } + if (msg == WM_RBUTTONUP) { button = 1; } + if (msg == WM_MBUTTONUP) { button = 2; } + if (msg == WM_XBUTTONUP) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } + io.MouseDown[button] = false; + if (!ImGui::IsAnyMouseDown() && ::GetCapture() == hwnd) + ::ReleaseCapture(); + return 0; + } + case WM_MOUSEWHEEL: + io.MouseWheel += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + return 0; + case WM_MOUSEHWHEEL: + io.MouseWheelH += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + return 0; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (wParam < 256) + io.KeysDown[wParam] = 1; + return 0; + case WM_KEYUP: + case WM_SYSKEYUP: + if (wParam < 256) + io.KeysDown[wParam] = 0; + return 0; + case WM_CHAR: + // You can also use ToAscii()+GetKeyboardState() to retrieve characters. + if (wParam > 0 && wParam < 0x10000) + io.AddInputCharacterUTF16((unsigned short)wParam); + return 0; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor()) + return 1; + return 0; + case WM_DEVICECHANGE: + if ((UINT)wParam == DBT_DEVNODES_CHANGED) + g_WantUpdateHasGamepad = true; + return 0; + case WM_KILLFOCUS: + for (int n = 0; n < IM_ARRAYSIZE(io.KeysDown); n++) + io.KeysDown[n] = false; + break; + } + return 0; +} + + +//-------------------------------------------------------------------------------------------------------- +// DPI-related helpers (optional) +//-------------------------------------------------------------------------------------------------------- +// - Use to enable DPI awareness without having to create an application manifest. +// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. +// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. +// but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, +// neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. +//--------------------------------------------------------------------------------------------------------- +// This is the scheme successfully used by GLFW (from which we borrowed some of the code) and other apps aiming to be highly portable. +// ImGui_ImplWin32_EnableDpiAwareness() is just a helper called by main.cpp, we don't call it automatically. +// If you are trying to implement your own back-end for your own engine, you may ignore that noise. +//--------------------------------------------------------------------------------------------------------- + +// Implement some of the functions and types normally declared in recent Windows SDK. +#if !defined(_versionhelpers_H_INCLUDED_) && !defined(_INC_VERSIONHELPERS) +static BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, { 0 }, sp, 0, 0, 0, 0 }; + DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR; + ULONGLONG cond = ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL); + cond = ::VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL); + cond = ::VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + return ::VerifyVersionInfoW(&osvi, mask, cond); +} +#define IsWindows8Point1OrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WINBLUE +#endif + +#ifndef DPI_ENUMS_DECLARED +typedef enum { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; +typedef enum { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } MONITOR_DPI_TYPE; +#endif +#ifndef _DPI_AWARENESS_CONTEXTS_ +DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE (DPI_AWARENESS_CONTEXT)-3 +#endif +#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 (DPI_AWARENESS_CONTEXT)-4 +#endif +typedef HRESULT(WINAPI* PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); // Shcore.lib + dll, Windows 8.1+ +typedef HRESULT(WINAPI* PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); // Shcore.lib + dll, Windows 8.1+ +typedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1607+ (Creators Update) + +// Helper function to enable DPI awareness without setting up a manifest +void ImGui_ImplWin32_EnableDpiAwareness() +{ + // if (IsWindows10OrGreater()) // This needs a manifest to succeed. Instead we try to grab the function pointer! + { + static HINSTANCE user32_dll = ::LoadLibraryA("user32.dll"); // Reference counted per-process + if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) + { + SetThreadDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + return; + } + } + if (IsWindows8Point1OrGreater()) + { + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, "SetProcessDpiAwareness")) + { + SetProcessDpiAwarenessFn(PROCESS_PER_MONITOR_DPI_AWARE); + return; + } + } +#if _WIN32_WINNT >= 0x0600 + ::SetProcessDPIAware(); +#endif +} + +#if defined(_MSC_VER) && !defined(NOGDI) +#pragma comment(lib, "gdi32") // Link with gdi32.lib for GetDeviceCaps() +#endif + +float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) +{ + UINT xdpi = 96, ydpi = 96; + static BOOL bIsWindows8Point1OrGreater = IsWindows8Point1OrGreater(); + if (bIsWindows8Point1OrGreater) + { + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + if (PFN_GetDpiForMonitor GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor")) + GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + } +#ifndef NOGDI + else + { + const HDC dc = ::GetDC(NULL); + xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); + ydpi = ::GetDeviceCaps(dc, LOGPIXELSY); + ::ReleaseDC(NULL, dc); + } +#endif + IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! + return xdpi / 96.0f; +} + +float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd) +{ + HMONITOR monitor = ::MonitorFromWindow((HWND)hwnd, MONITOR_DEFAULTTONEAREST); + return ImGui_ImplWin32_GetDpiScaleForMonitor(monitor); +} + +//--------------------------------------------------------------------------------------------------------- diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.h b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.h new file mode 100644 index 00000000..8923bd63 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/imgui_impl_win32.h @@ -0,0 +1,37 @@ +// dear imgui: Platform Binding for Windows (standard windows API for 32 and 64 bits applications) +// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) + +// Implemented features: +// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE). +// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); +IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); + +// Configuration +// - Disable gamepad support or linking with xinput.lib +//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +//#define IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT + +// Win32 message handler your application need to call. +// - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on from this helper. +// - You should COPY the line below into your .cpp code to forward declare the function and then you can call it. +#if 0 +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +#endif + +// DPI-related helpers (optional) +// - Use to enable DPI awareness without having to create an application manifest. +// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. +// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. +// but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, +// neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. +IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness(); +IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd +IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/platform.h b/cpp/vendor/imgui-node-editor/examples/application/source/platform.h new file mode 100644 index 00000000..79afd16d --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/platform.h @@ -0,0 +1,60 @@ +# pragma once +# include "setup.h" +# include + +struct Application; +struct Renderer; + +struct Platform +{ + virtual ~Platform() {}; + + virtual bool ApplicationStart(int argc, char** argv) = 0; + virtual void ApplicationStop() = 0; + + virtual bool OpenMainWindow(const char* title, int width, int height) = 0; + virtual bool CloseMainWindow() = 0; + virtual void* GetMainWindowHandle() const = 0; + virtual void SetMainWindowTitle(const char* title) = 0; + virtual void ShowMainWindow() = 0; + virtual bool ProcessMainWindowEvents() = 0; + virtual bool IsMainWindowVisible() const = 0; + + virtual void SetRenderer(Renderer* renderer) = 0; + + virtual void NewFrame() = 0; + virtual void FinishFrame() = 0; + + virtual void Quit() = 0; + + bool HasWindowScaleChanged() const { return m_WindowScaleChanged; } + void AcknowledgeWindowScaleChanged() { m_WindowScaleChanged = false; } + float GetWindowScale() const { return m_WindowScale; } + void SetWindowScale(float windowScale) + { + if (windowScale == m_WindowScale) + return; + m_WindowScale = windowScale; + m_WindowScaleChanged = true; + } + + bool HasFramebufferScaleChanged() const { return m_FramebufferScaleChanged; } + void AcknowledgeFramebufferScaleChanged() { m_FramebufferScaleChanged = false; } + float GetFramebufferScale() const { return m_FramebufferScale; } + void SetFramebufferScale(float framebufferScale) + { + if (framebufferScale == m_FramebufferScale) + return; + m_FramebufferScale = framebufferScale; + m_FramebufferScaleChanged = true; + } + + +private: + bool m_WindowScaleChanged = false; + float m_WindowScale = 1.0f; + bool m_FramebufferScaleChanged = false; + float m_FramebufferScale = 1.0f; +}; + +std::unique_ptr CreatePlatform(Application& application); diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/platform_glfw.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/platform_glfw.cpp new file mode 100644 index 00000000..6fe71b52 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/platform_glfw.cpp @@ -0,0 +1,287 @@ +# include "platform.h" +# include "setup.h" + +# if BACKEND(IMGUI_GLFW) + +# include "application.h" +# include "renderer.h" + +# include + +# if PLATFORM(WINDOWS) +# define GLFW_EXPOSE_NATIVE_WIN32 +# include +# endif + +# include +# include "imgui_impl_glfw.h" + +struct PlatformGLFW final + : Platform +{ + static PlatformGLFW* s_Instance; + + PlatformGLFW(Application& application); + + bool ApplicationStart(int argc, char** argv) override; + void ApplicationStop() override; + bool OpenMainWindow(const char* title, int width, int height) override; + bool CloseMainWindow() override; + void* GetMainWindowHandle() const override; + void SetMainWindowTitle(const char* title) override; + void ShowMainWindow() override; + bool ProcessMainWindowEvents() override; + bool IsMainWindowVisible() const override; + void SetRenderer(Renderer* renderer) override; + void NewFrame() override; + void FinishFrame() override; + void Quit() override; + + void UpdatePixelDensity(); + + Application& m_Application; + GLFWwindow* m_Window = nullptr; + bool m_QuitRequested = false; + bool m_IsMinimized = false; + bool m_WasMinimized = false; + Renderer* m_Renderer = nullptr; +}; + +std::unique_ptr CreatePlatform(Application& application) +{ + return std::make_unique(application); +} + +PlatformGLFW::PlatformGLFW(Application& application) + : m_Application(application) +{ +} + +bool PlatformGLFW::ApplicationStart(int argc, char** argv) +{ + if (!glfwInit()) + return false; + + return true; +} + +void PlatformGLFW::ApplicationStop() +{ + glfwTerminate(); +} + +bool PlatformGLFW::OpenMainWindow(const char* title, int width, int height) +{ + if (m_Window) + return false; + + glfwWindowHint(GLFW_VISIBLE, 0); + + using InitializerType = bool (*)(GLFWwindow* window, bool install_callbacks); + + InitializerType initializer = nullptr; + +# if RENDERER(IMGUI_OGL3) + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +# if PLATFORM(MACOS) + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +# ifdef GLFW_COCOA_RETINA_FRAMEBUFFER + glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GL_TRUE); +# endif +# ifdef GLFW_COCOA_GRAPHICS_SWITCHING + glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, GL_TRUE); +# endif +# endif + initializer = &ImGui_ImplGlfw_InitForOpenGL; +# else + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + initializer = &ImGui_ImplGlfw_InitForNone; +# endif + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GL_TRUE); + + width = width < 0 ? 1440 : width; + height = height < 0 ? 800 : height; + + m_Window = glfwCreateWindow(width, height, title, nullptr, nullptr); + if (!m_Window) + return false; + + if (!initializer || !initializer(m_Window, true)) + { + glfwDestroyWindow(m_Window); + m_Window = nullptr; + return false; + } + + glfwSetWindowUserPointer(m_Window, this); + + glfwSetWindowCloseCallback(m_Window, [](GLFWwindow* window) + { + auto self = reinterpret_cast(glfwGetWindowUserPointer(window)); + if (!self->m_QuitRequested) + self->CloseMainWindow(); + }); + + glfwSetWindowIconifyCallback(m_Window, [](GLFWwindow* window, int iconified) + { + auto self = reinterpret_cast(glfwGetWindowUserPointer(window)); + if (iconified) + { + self->m_IsMinimized = true; + self->m_WasMinimized = true; + } + else + { + self->m_IsMinimized = false; + } + }); + + auto onFramebuferSizeChanged = [](GLFWwindow* window, int width, int height) + { + auto self = reinterpret_cast(glfwGetWindowUserPointer(window)); + if (self->m_Renderer) + { + self->m_Renderer->Resize(width, height); + self->UpdatePixelDensity(); + } + }; + + glfwSetFramebufferSizeCallback(m_Window, onFramebuferSizeChanged); + + auto onWindowContentScaleChanged = [](GLFWwindow* window, float xscale, float yscale) + { + auto self = reinterpret_cast(glfwGetWindowUserPointer(window)); + self->UpdatePixelDensity(); + }; + + glfwSetWindowContentScaleCallback(m_Window, onWindowContentScaleChanged); + + UpdatePixelDensity(); + + glfwMakeContextCurrent(m_Window); + + glfwSwapInterval(1); // Enable vsync + + return true; +} + +bool PlatformGLFW::CloseMainWindow() +{ + if (m_Window == nullptr) + return true; + + auto canClose = m_Application.CanClose(); + + glfwSetWindowShouldClose(m_Window, canClose ? 1 : 0); + + return canClose; +} + +void* PlatformGLFW::GetMainWindowHandle() const +{ +# if PLATFORM(WINDOWS) + return m_Window ? glfwGetWin32Window(m_Window) : nullptr; +# else + return nullptr; +# endif +} + +void PlatformGLFW::SetMainWindowTitle(const char* title) +{ + glfwSetWindowTitle(m_Window, title); +} + +void PlatformGLFW::ShowMainWindow() +{ + if (m_Window == nullptr) + return; + + glfwShowWindow(m_Window); +} + +bool PlatformGLFW::ProcessMainWindowEvents() +{ + if (m_Window == nullptr) + return false; + + if (m_IsMinimized) + glfwWaitEvents(); + else + glfwPollEvents(); + + if (m_QuitRequested || glfwWindowShouldClose(m_Window)) + { + ImGui_ImplGlfw_Shutdown(); + + glfwDestroyWindow(m_Window); + + return false; + } + + return true; +} + +bool PlatformGLFW::IsMainWindowVisible() const +{ + if (m_Window == nullptr) + return false; + + if (m_IsMinimized) + return false; + + return true; +} + +void PlatformGLFW::SetRenderer(Renderer* renderer) +{ + m_Renderer = renderer; +} + +void PlatformGLFW::NewFrame() +{ + ImGui_ImplGlfw_NewFrame(); + + if (m_WasMinimized) + { + ImGui::GetIO().DeltaTime = 0.1e-6f; + m_WasMinimized = false; + } +} + +void PlatformGLFW::FinishFrame() +{ + if (m_Renderer) + m_Renderer->Present(); + + glfwSwapBuffers(m_Window); +} + +void PlatformGLFW::Quit() +{ + m_QuitRequested = true; + + glfwPostEmptyEvent(); +} + +void PlatformGLFW::UpdatePixelDensity() +{ + float xscale, yscale; + glfwGetWindowContentScale(m_Window, &xscale, &yscale); + float scale = xscale > yscale ? xscale : yscale; + +# if PLATFORM(WINDOWS) + float windowScale = scale; + float framebufferScale = scale; +# else + float windowScale = 1.0f; + float framebufferScale = scale; +# endif + + SetWindowScale(windowScale); // this is how windows is scaled, not window content + + SetFramebufferScale(framebufferScale); +} + +# endif // BACKEND(IMGUI_GLFW) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/platform_win32.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/platform_win32.cpp new file mode 100644 index 00000000..f8d05f5c --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/platform_win32.cpp @@ -0,0 +1,313 @@ +# include "platform.h" +# include "setup.h" + +# if BACKEND(IMGUI_WIN32) + +# include "application.h" +# include "renderer.h" + +# define NOMINMAX +# define WIN32_LEAN_AND_MEAN +# include +# include +# include + +# include +# include "imgui_impl_win32.h" + +# if defined(_UNICODE) +std::wstring Utf8ToNative(const std::string& str) +{ + int size = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), nullptr, 0); + std::wstring result(size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), (wchar_t*)result.data(), size); + return result; +} +# else +std::string Utf8ToNative(const std::string& str) +{ + return str; +} +# endif + +IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +struct PlatformWin32 final + : Platform +{ + static PlatformWin32* s_Instance; + + PlatformWin32(Application& application); + + bool ApplicationStart(int argc, char** argv) override; + void ApplicationStop() override; + bool OpenMainWindow(const char* title, int width, int height) override; + bool CloseMainWindow() override; + void* GetMainWindowHandle() const override; + void SetMainWindowTitle(const char* title) override; + void ShowMainWindow() override; + bool ProcessMainWindowEvents() override; + bool IsMainWindowVisible() const override; + void SetRenderer(Renderer* renderer) override; + void NewFrame() override; + void FinishFrame() override; + void Quit() override; + + void SetDpiScale(float dpiScale); + + LRESULT WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + Application& m_Application; + WNDCLASSEX m_WindowClass = {}; + HWND m_MainWindowHandle = nullptr; + bool m_IsMinimized = false; + bool m_WasMinimized = false; + bool m_CanCloseResult = false; + Renderer* m_Renderer = nullptr; +}; + +std::unique_ptr CreatePlatform(Application& application) +{ + return std::make_unique(application); +} + +PlatformWin32* PlatformWin32::s_Instance = nullptr; + +PlatformWin32::PlatformWin32(Application& application) + : m_Application(application) +{ +} + +bool PlatformWin32::ApplicationStart(int argc, char** argv) +{ + if (s_Instance) + return false; + + s_Instance = this; + + auto winProc = [](HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT + { + return s_Instance->WinProc(hWnd, msg, wParam, lParam); + }; + + m_WindowClass = + { + sizeof(WNDCLASSEX), + CS_CLASSDC, + winProc, + 0L, + 0L, + GetModuleHandle(nullptr), + LoadIcon(GetModuleHandle(nullptr), + IDI_APPLICATION), + LoadCursor(nullptr, IDC_ARROW), + nullptr, + nullptr, + _T("imgui-node-editor-application"), + LoadIcon(GetModuleHandle(nullptr), + IDI_APPLICATION) + }; + + if (!RegisterClassEx(&m_WindowClass)) + { + s_Instance = nullptr; + return false; + } + + ImGui_ImplWin32_EnableDpiAwareness(); + + return true; +} + +void PlatformWin32::ApplicationStop() +{ + if (!s_Instance) + return; + + UnregisterClass(m_WindowClass.lpszClassName, m_WindowClass.hInstance); + + s_Instance = nullptr; +} + + +bool PlatformWin32::OpenMainWindow(const char* title, int width, int height) +{ + if (m_MainWindowHandle) + return false; + + m_MainWindowHandle = CreateWindow( + m_WindowClass.lpszClassName, + Utf8ToNative(title).c_str(), + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + width < 0 ? CW_USEDEFAULT : width, + height < 0 ? CW_USEDEFAULT : height, + nullptr, nullptr, m_WindowClass.hInstance, nullptr); + + if (!m_MainWindowHandle) + return false; + + if (!ImGui_ImplWin32_Init(m_MainWindowHandle)) + { + DestroyWindow(m_MainWindowHandle); + m_MainWindowHandle = nullptr; + return false; + } + + SetDpiScale(ImGui_ImplWin32_GetDpiScaleForHwnd(m_MainWindowHandle)); + + return true; +} + +bool PlatformWin32::CloseMainWindow() +{ + if (m_MainWindowHandle == nullptr) + return true; + + SendMessage(m_MainWindowHandle, WM_CLOSE, 0, 0); + + return m_CanCloseResult; +} + +void* PlatformWin32::GetMainWindowHandle() const +{ + return m_MainWindowHandle; +} + +void PlatformWin32::SetMainWindowTitle(const char* title) +{ + SetWindowText(m_MainWindowHandle, Utf8ToNative(title).c_str()); +} + +void PlatformWin32::ShowMainWindow() +{ + if (m_MainWindowHandle == nullptr) + return; + + //ShowWindow(m_MainWindowHandle, SW_SHOWMAXIMIZED); + ShowWindow(m_MainWindowHandle, SW_SHOW); + UpdateWindow(m_MainWindowHandle); +} + +bool PlatformWin32::ProcessMainWindowEvents() +{ + if (m_MainWindowHandle == nullptr) + return false; + + auto fetchMessage = [this](MSG* msg) -> bool + { + if (!m_IsMinimized) + return PeekMessage(msg, nullptr, 0U, 0U, PM_REMOVE) != 0; + else + return GetMessage(msg, nullptr, 0U, 0U) != 0; + }; + + MSG msg = {}; + while (fetchMessage(&msg)) + { + if (msg.message == WM_KEYDOWN && (msg.wParam == VK_ESCAPE)) + PostQuitMessage(0); + + if (msg.message == WM_QUIT) + return false; + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return true; +} + +bool PlatformWin32::IsMainWindowVisible() const +{ + if (m_MainWindowHandle == nullptr) + return false; + + if (m_IsMinimized) + return false; + + return true; +} + +void PlatformWin32::SetRenderer(Renderer* renderer) +{ + m_Renderer = renderer; +} + +void PlatformWin32::NewFrame() +{ + ImGui_ImplWin32_NewFrame(); + + if (m_WasMinimized) + { + ImGui::GetIO().DeltaTime = 0.1e-6f; + m_WasMinimized = false; + } +} + +void PlatformWin32::FinishFrame() +{ + if (m_Renderer) + m_Renderer->Present(); +} + +void PlatformWin32::Quit() +{ + PostQuitMessage(0); +} + +void PlatformWin32::SetDpiScale(float dpiScale) +{ + SetWindowScale(dpiScale); + SetFramebufferScale(dpiScale); +} + +LRESULT PlatformWin32::WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) + return 1; + + switch (msg) + { + case WM_CLOSE: + m_CanCloseResult = m_Application.CanClose(); + if (m_CanCloseResult) + { + ImGui_ImplWin32_Shutdown(); + DestroyWindow(hWnd); + } + return 0; + + case WM_SIZE: + if (wParam == SIZE_MINIMIZED) + { + m_IsMinimized = true; + m_WasMinimized = true; + } + else if (wParam == SIZE_RESTORED && m_IsMinimized) + { + m_IsMinimized = false; + } + + if (m_Renderer != nullptr && wParam != SIZE_MINIMIZED) + m_Renderer->Resize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); + return 0; + + case WM_SYSCOMMAND: + if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu + return 0; + break; + + case WM_DPICHANGED: + SetDpiScale(ImGui_ImplWin32_GetDpiScaleForHwnd(hWnd)); + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +# endif // BACKEND(IMGUI_WIN32) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/renderer.h b/cpp/vendor/imgui-node-editor/examples/application/source/renderer.h new file mode 100644 index 00000000..30097379 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/renderer.h @@ -0,0 +1,32 @@ +# pragma once +# include "setup.h" +# include + +struct Platform; +struct ImDrawData; +struct ImVec4; +using ImTextureID= void*; + +struct Renderer +{ + virtual ~Renderer() {}; + + virtual bool Create(Platform& platform) = 0; + virtual void Destroy() = 0; + + virtual void NewFrame() = 0; + + virtual void RenderDrawData(ImDrawData* drawData) = 0; + + virtual void Clear(const ImVec4& color) = 0; + virtual void Present() = 0; + + virtual void Resize(int width, int height) = 0; + + virtual ImTextureID CreateTexture(const void* data, int width, int height) = 0; + virtual void DestroyTexture(ImTextureID texture) = 0; + virtual int GetTextureWidth(ImTextureID texture) = 0; + virtual int GetTextureHeight(ImTextureID texture) = 0; +}; + +std::unique_ptr CreateRenderer(); diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/renderer_dx11.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/renderer_dx11.cpp new file mode 100644 index 00000000..473c5ac4 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/renderer_dx11.cpp @@ -0,0 +1,195 @@ +# include "renderer.h" +# include "setup.h" + +# if RENDERER(IMGUI_DX11) + +# include "platform.h" + +# if PLATFORM(WINDOWS) +# define NOMINMAX +# define WIN32_LEAN_AND_MEAN +# include +# endif + +# include +# include "imgui_impl_dx11.h" +# include + + +struct RendererDX11 final + : Renderer +{ + bool Create(Platform& platform) override; + void Destroy() override; + void NewFrame() override; + void RenderDrawData(ImDrawData* drawData) override; + void Clear(const ImVec4& color) override; + void Present() override; + void Resize(int width, int height) override; + + ImTextureID CreateTexture(const void* data, int width, int height) override; + void DestroyTexture(ImTextureID texture) override; + int GetTextureWidth(ImTextureID texture) override; + int GetTextureHeight(ImTextureID texture) override; + + HRESULT CreateDeviceD3D(HWND hWnd); + void CleanupDeviceD3D(); + + void CreateRenderTarget(); + void CleanupRenderTarget(); + + Platform* m_Platform = nullptr; + ID3D11Device* m_device = nullptr; + ID3D11DeviceContext* m_deviceContext = nullptr; + IDXGISwapChain* m_swapChain = nullptr; + ID3D11RenderTargetView* m_mainRenderTargetView = nullptr; +}; + +std::unique_ptr CreateRenderer() +{ + return std::make_unique(); +} + +bool RendererDX11::Create(Platform& platform) +{ + m_Platform = &platform; + + auto hr = CreateDeviceD3D(reinterpret_cast(platform.GetMainWindowHandle())); + if (FAILED(hr)) + return false; + + if (!ImGui_ImplDX11_Init(m_device, m_deviceContext)) + { + CleanupDeviceD3D(); + return false; + } + + m_Platform->SetRenderer(this); + + return true; +} + +void RendererDX11::Destroy() +{ + if (!m_Platform) + return; + + m_Platform->SetRenderer(nullptr); + + ImGui_ImplDX11_Shutdown(); + + CleanupDeviceD3D(); +} + +void RendererDX11::NewFrame() +{ + ImGui_ImplDX11_NewFrame(); +} + +void RendererDX11::RenderDrawData(ImDrawData* drawData) +{ + ImGui_ImplDX11_RenderDrawData(drawData); +} + +void RendererDX11::Clear(const ImVec4& color) +{ + m_deviceContext->ClearRenderTargetView(m_mainRenderTargetView, (float*)&color.x); +} + +void RendererDX11::Present() +{ + m_swapChain->Present(1, 0); +} + +void RendererDX11::Resize(int width, int height) +{ + ImGui_ImplDX11_InvalidateDeviceObjects(); + CleanupRenderTarget(); + m_swapChain->ResizeBuffers(0, (UINT)width, (UINT)height, DXGI_FORMAT_UNKNOWN, 0); + CreateRenderTarget(); +} + +HRESULT RendererDX11::CreateDeviceD3D(HWND hWnd) +{ + // Setup swap chain + DXGI_SWAP_CHAIN_DESC sd; + { + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = 2; + sd.BufferDesc.Width = 0; + sd.BufferDesc.Height = 0; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow = hWnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + } + + UINT createDeviceFlags = 0; + //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + D3D_FEATURE_LEVEL featureLevel; + const D3D_FEATURE_LEVEL featureLevelArray[1] = { D3D_FEATURE_LEVEL_11_0, }; + if (D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 1, D3D11_SDK_VERSION, &sd, &m_swapChain, &m_device, &featureLevel, &m_deviceContext) != S_OK) + return E_FAIL; + + CreateRenderTarget(); + + return S_OK; +} + +void RendererDX11::CleanupDeviceD3D() +{ + CleanupRenderTarget(); + if (m_swapChain) { m_swapChain->Release(); m_swapChain = nullptr; } + if (m_deviceContext) { m_deviceContext->Release(); m_deviceContext = nullptr; } + if (m_device) { m_device->Release(); m_device = nullptr; } +} + +void RendererDX11::CreateRenderTarget() +{ + DXGI_SWAP_CHAIN_DESC sd; + m_swapChain->GetDesc(&sd); + + // Create the render target + ID3D11Texture2D* pBackBuffer; + D3D11_RENDER_TARGET_VIEW_DESC render_target_view_desc; + ZeroMemory(&render_target_view_desc, sizeof(render_target_view_desc)); + render_target_view_desc.Format = sd.BufferDesc.Format; + render_target_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); + m_device->CreateRenderTargetView(pBackBuffer, &render_target_view_desc, &m_mainRenderTargetView); + m_deviceContext->OMSetRenderTargets(1, &m_mainRenderTargetView, nullptr); + pBackBuffer->Release(); +} + +void RendererDX11::CleanupRenderTarget() +{ + if (m_mainRenderTargetView) { m_mainRenderTargetView->Release(); m_mainRenderTargetView = nullptr; } +} + +ImTextureID RendererDX11::CreateTexture(const void* data, int width, int height) +{ + return ImGui_CreateTexture(data, width, height); +} + +void RendererDX11::DestroyTexture(ImTextureID texture) +{ + return ImGui_DestroyTexture(texture); +} + +int RendererDX11::GetTextureWidth(ImTextureID texture) +{ + return ImGui_GetTextureWidth(texture); +} + +int RendererDX11::GetTextureHeight(ImTextureID texture) +{ + return ImGui_GetTextureHeight(texture); +} + +# endif // RENDERER(IMGUI_DX11) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/renderer_ogl3.cpp b/cpp/vendor/imgui-node-editor/examples/application/source/renderer_ogl3.cpp new file mode 100644 index 00000000..8ea98d81 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/renderer_ogl3.cpp @@ -0,0 +1,208 @@ +# include "renderer.h" + +# if RENDERER(IMGUI_OGL3) + +# include "platform.h" +# include +# include // std::intptr_t + +# if PLATFORM(WINDOWS) +# define NOMINMAX +# define WIN32_LEAN_AND_MEAN +# include +# endif + +# include "imgui_impl_opengl3.h" + +# if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +# include // Initialize with gl3wInit() +# elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) +# include // Initialize with glewInit() +# elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) +# include // Initialize with gladLoadGL() +# elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) +# include // Initialize with gladLoadGL(...) or gladLoaderLoadGL() +# elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) +# define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. +# include // Initialize with glbinding::Binding::initialize() +# include +using namespace gl; +# elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) +# define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. +# include // Initialize with glbinding::initialize() +# include +using namespace gl; +# elif defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) +# include IMGUI_IMPL_OPENGL_LOADER_CUSTOM +# else +# include "imgui_impl_opengl3_loader.h" +# endif + +struct ImTexture +{ + GLuint TextureID = 0; + int Width = 0; + int Height = 0; +}; + +struct RendererOpenGL3 final + : Renderer +{ + bool Create(Platform& platform) override; + void Destroy() override; + void NewFrame() override; + void RenderDrawData(ImDrawData* drawData) override; + void Clear(const ImVec4& color) override; + void Present() override; + void Resize(int width, int height) override; + + ImVector::iterator FindTexture(ImTextureID texture); + ImTextureID CreateTexture(const void* data, int width, int height) override; + void DestroyTexture(ImTextureID texture) override; + int GetTextureWidth(ImTextureID texture) override; + int GetTextureHeight(ImTextureID texture) override; + + Platform* m_Platform = nullptr; + ImVector m_Textures; +}; + +std::unique_ptr CreateRenderer() +{ + return std::make_unique(); +} + +bool RendererOpenGL3::Create(Platform& platform) +{ + m_Platform = &platform; + + // Technically we should initialize OpenGL context here, + // but for now we relay on one created by GLFW3 + +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) + bool err = gl3wInit() != 0; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) + bool err = glewInit() != GLEW_OK; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) + bool err = gladLoadGL() == 0; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD2) + bool err = gladLoadGL(glfwGetProcAddress) == 0; // glad2 recommend using the windowing library loader instead of the (optionally) bundled one. +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) + bool err = false; + glbinding::Binding::initialize(); +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) + bool err = false; + glbinding::initialize([](const char* name) { return (glbinding::ProcAddress)glfwGetProcAddress(name); }); +#else + bool err = false; // If you use IMGUI_IMPL_OPENGL_LOADER_CUSTOM, your loader is likely to requires some form of initialization. +#endif + if (err) + return false; + +# if PLATFORM(MACOS) + const char* glslVersion = "#version 150"; +# else + const char* glslVersion = "#version 130"; +# endif + + if (!ImGui_ImplOpenGL3_Init(glslVersion)) + return false; + + m_Platform->SetRenderer(this); + + return true; +} + +void RendererOpenGL3::Destroy() +{ + if (!m_Platform) + return; + + m_Platform->SetRenderer(nullptr); + + ImGui_ImplOpenGL3_Shutdown(); +} + +void RendererOpenGL3::NewFrame() +{ + ImGui_ImplOpenGL3_NewFrame(); +} + +void RendererOpenGL3::RenderDrawData(ImDrawData* drawData) +{ + ImGui_ImplOpenGL3_RenderDrawData(drawData); +} + +void RendererOpenGL3::Clear(const ImVec4& color) +{ + glClearColor(color.x, color.y, color.z, color.w); + glClear(GL_COLOR_BUFFER_BIT); +} + +void RendererOpenGL3::Present() +{ +} + +void RendererOpenGL3::Resize(int width, int height) +{ + glViewport(0, 0, width, height); +} + +ImTextureID RendererOpenGL3::CreateTexture(const void* data, int width, int height) +{ + m_Textures.resize(m_Textures.size() + 1); + ImTexture& texture = m_Textures.back(); + + // Upload texture to graphics system + GLint last_texture = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &texture.TextureID); + glBindTexture(GL_TEXTURE_2D, texture.TextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glBindTexture(GL_TEXTURE_2D, last_texture); + + texture.Width = width; + texture.Height = height; + + return reinterpret_cast(static_cast(texture.TextureID)); +} + +ImVector::iterator RendererOpenGL3::FindTexture(ImTextureID texture) +{ + auto textureID = static_cast(reinterpret_cast(texture)); + + return std::find_if(m_Textures.begin(), m_Textures.end(), [textureID](ImTexture& texture) + { + return texture.TextureID == textureID; + }); +} + +void RendererOpenGL3::DestroyTexture(ImTextureID texture) +{ + auto textureIt = FindTexture(texture); + if (textureIt == m_Textures.end()) + return; + + glDeleteTextures(1, &textureIt->TextureID); + + m_Textures.erase(textureIt); +} + +int RendererOpenGL3::GetTextureWidth(ImTextureID texture) +{ + auto textureIt = FindTexture(texture); + if (textureIt != m_Textures.end()) + return textureIt->Width; + return 0; +} + +int RendererOpenGL3::GetTextureHeight(ImTextureID texture) +{ + auto textureIt = FindTexture(texture); + if (textureIt != m_Textures.end()) + return textureIt->Height; + return 0; +} + +# endif // RENDERER(IMGUI_OGL3) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/application/source/setup.h b/cpp/vendor/imgui-node-editor/examples/application/source/setup.h new file mode 100644 index 00000000..2286ba40 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/source/setup.h @@ -0,0 +1,98 @@ +# pragma once +# include "config.h" + +# define DETAIL_PRIV_EXPAND(x) x +# define EXPAND(x) DETAIL_PRIV_EXPAND(x) +# define DETAIL_PRIV_CONCAT(x, y) x ## y +# define CONCAT(x, y) DETAIL_PRIV_CONCAT(x, y) + + +// Define PLATFORM(x) which evaluate to 0 or 1 when +// 'x' is: WINDOWS, MACOS or LINUX +# if defined(_WIN32) +# define PLATFORM_PRIV_WINDOWS() 1 +# elif defined(__APPLE__) +# define PLATFORM_PRIV_MACOS() 1 +# elif defined(__linux__) +# define PLATFORM_PRIV_LINUX() 1 +# else +# error Unsupported platform +# endif + +# ifndef PLATFORM_PRIV_WINDOWS +# define PLATFORM_PRIV_WINDOWS() 0 +# endif +# ifndef PLATFORM_PRIV_MACOS +# define PLATFORM_PRIV_MACOS() 0 +# endif +# ifndef PLATFORM_PRIV_LINUX +# define PLATFORM_PRIV_LINUX() 0 +# endif + +# define PLATFORM(x) (PLATFORM_PRIV_##x()) + + +// Define BACKEND(x) which evaluate to 0 or 1 when +// 'x' is: IMGUI_WIN32 or IMGUI_GLFW +// +// Use BACKEND_CONFIG to override desired backend +// +# if PLATFORM(WINDOWS) +# define BACKEND_HAVE_IMGUI_WIN32() 1 +# endif +# if HAVE_GLFW3 +# define BACKEND_HAVE_IMGUI_GLFW() 1 +# endif + +# ifndef BACKEND_HAVE_IMGUI_WIN32 +# define BACKEND_HAVE_IMGUI_WIN32() 0 +# endif +# ifndef BACKEND_HAVE_IMGUI_GLFW +# define BACKEND_HAVE_IMGUI_GLFW() 0 +# endif + +# define BACKEND_PRIV_IMGUI_WIN32() 1 +# define BACKEND_PRIV_IMGUI_GLFW() 2 + +# if !defined(BACKEND_CONFIG) +# if PLATFORM(WINDOWS) +# define BACKEND_CONFIG IMGUI_WIN32 +# else +# define BACKEND_CONFIG IMGUI_GLFW +# endif +# endif + +# define BACKEND(x) ((BACKEND_PRIV_##x()) == CONCAT(BACKEND_PRIV_, EXPAND(BACKEND_CONFIG))() && (BACKEND_HAVE_##x())) + + +// Define RENDERER(x) which evaluate to 0 or 1 when +// 'x' is: IMGUI_DX11 or IMGUI_OGL3 +// +// Use RENDERER_CONFIG to override desired renderer +// +# if PLATFORM(WINDOWS) +# define RENDERER_HAVE_IMGUI_DX11() 1 +# endif +# if HAVE_OPENGL +# define RENDERER_HAVE_IMGUI_OGL3() 1 +# endif + +# ifndef RENDERER_HAVE_IMGUI_DX11 +# define RENDERER_HAVE_IMGUI_DX11() 0 +# endif +# ifndef RENDERER_HAVE_IMGUI_OGL3 +# define RENDERER_HAVE_IMGUI_OGL3() 0 +# endif + +# define RENDERER_PRIV_IMGUI_DX11() 1 +# define RENDERER_PRIV_IMGUI_OGL3() 2 + +# if !defined(RENDERER_CONFIG) +# if PLATFORM(WINDOWS) +# define RENDERER_CONFIG IMGUI_DX11 +# else +# define RENDERER_CONFIG IMGUI_OGL3 +# endif +# endif + +# define RENDERER(x) ((RENDERER_PRIV_##x()) == CONCAT(RENDERER_PRIV_, EXPAND(RENDERER_CONFIG))() && (RENDERER_HAVE_##x())) diff --git a/cpp/vendor/imgui-node-editor/examples/application/support/Icon.icns b/cpp/vendor/imgui-node-editor/examples/application/support/Icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..18e37c00a3665e0b61d1610921859f82fb716cd6 GIT binary patch literal 172448 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Ð$oI=x-8rBMj$s=Eu#^*Y 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 literal 0 HcmV?d00001 diff --git a/cpp/vendor/imgui-node-editor/examples/application/support/Icon.ico b/cpp/vendor/imgui-node-editor/examples/application/support/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0ace9f3ceef6c1b8e72e3a3f9bd841382c981a95 GIT binary patch literal 39257 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 JRHA4V{$Giw3u6EP literal 0 HcmV?d00001 diff --git a/cpp/vendor/imgui-node-editor/examples/application/support/Icon.png b/cpp/vendor/imgui-node-editor/examples/application/support/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a532a630d4762f79fc218e1e46da5ab8d494d0d0 GIT binary patch literal 52666 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 + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSSupportsAutomaticGraphicsSwitching + + NSHighResolutionCapable + + + + diff --git a/cpp/vendor/imgui-node-editor/examples/application/support/Resource.rc.in b/cpp/vendor/imgui-node-editor/examples/application/support/Resource.rc.in new file mode 100644 index 00000000..a7809b97 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/application/support/Resource.rc.in @@ -0,0 +1,3 @@ +#define IDI_APPLICATION 32512 + +IDI_APPLICATION ICON "${ApplicationIcon}" diff --git a/cpp/vendor/imgui-node-editor/examples/basic-interaction-example/CMakeLists.txt b/cpp/vendor/imgui-node-editor/examples/basic-interaction-example/CMakeLists.txt new file mode 100644 index 00000000..4c79ebaa --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/basic-interaction-example/CMakeLists.txt @@ -0,0 +1,3 @@ +add_example_executable(basic-interaction-example + basic-interaction-example.cpp +) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/basic-interaction-example/basic-interaction-example.cpp b/cpp/vendor/imgui-node-editor/examples/basic-interaction-example/basic-interaction-example.cpp new file mode 100644 index 00000000..42535335 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/basic-interaction-example/basic-interaction-example.cpp @@ -0,0 +1,216 @@ +# include +# include +# include + +namespace ed = ax::NodeEditor; + +struct Example: + public Application +{ + // Struct to hold basic information about connection between + // pins. Note that connection (aka. link) has its own ID. + // This is useful later with dealing with selections, deletion + // or other operations. + struct LinkInfo + { + ed::LinkId Id; + ed::PinId InputId; + ed::PinId OutputId; + }; + + using Application::Application; + + void OnStart() override + { + ed::Config config; + config.SettingsFile = "BasicInteraction.json"; + m_Context = ed::CreateEditor(&config); + } + + void OnStop() override + { + ed::DestroyEditor(m_Context); + } + + void ImGuiEx_BeginColumn() + { + ImGui::BeginGroup(); + } + + void ImGuiEx_NextColumn() + { + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + } + + void ImGuiEx_EndColumn() + { + ImGui::EndGroup(); + } + + void OnFrame(float deltaTime) override + { + auto& io = ImGui::GetIO(); + + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + + ImGui::Separator(); + + ed::SetCurrentEditor(m_Context); + + // Start interaction with editor. + ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + + int uniqueId = 1; + + // + // 1) Commit known data to editor + // + + // Submit Node A + ed::NodeId nodeA_Id = uniqueId++; + ed::PinId nodeA_InputPinId = uniqueId++; + ed::PinId nodeA_OutputPinId = uniqueId++; + + if (m_FirstFrame) + ed::SetNodePosition(nodeA_Id, ImVec2(10, 10)); + ed::BeginNode(nodeA_Id); + ImGui::Text("Node A"); + ed::BeginPin(nodeA_InputPinId, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ed::BeginPin(nodeA_OutputPinId, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + ed::EndNode(); + + // Submit Node B + ed::NodeId nodeB_Id = uniqueId++; + ed::PinId nodeB_InputPinId1 = uniqueId++; + ed::PinId nodeB_InputPinId2 = uniqueId++; + ed::PinId nodeB_OutputPinId = uniqueId++; + + if (m_FirstFrame) + ed::SetNodePosition(nodeB_Id, ImVec2(210, 60)); + ed::BeginNode(nodeB_Id); + ImGui::Text("Node B"); + ImGuiEx_BeginColumn(); + ed::BeginPin(nodeB_InputPinId1, ed::PinKind::Input); + ImGui::Text("-> In1"); + ed::EndPin(); + ed::BeginPin(nodeB_InputPinId2, ed::PinKind::Input); + ImGui::Text("-> In2"); + ed::EndPin(); + ImGuiEx_NextColumn(); + ed::BeginPin(nodeB_OutputPinId, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + ImGuiEx_EndColumn(); + ed::EndNode(); + + // Submit Links + for (auto& linkInfo : m_Links) + ed::Link(linkInfo.Id, linkInfo.InputId, linkInfo.OutputId); + + // + // 2) Handle interactions + // + + // Handle creation action, returns true if editor want to create new object (node or link) + if (ed::BeginCreate()) + { + ed::PinId inputPinId, outputPinId; + if (ed::QueryNewLink(&inputPinId, &outputPinId)) + { + // QueryNewLink returns true if editor want to create new link between pins. + // + // Link can be created only for two valid pins, it is up to you to + // validate if connection make sense. Editor is happy to make any. + // + // Link always goes from input to output. User may choose to drag + // link from output pin or input pin. This determine which pin ids + // are valid and which are not: + // * input valid, output invalid - user started to drag new ling from input pin + // * input invalid, output valid - user started to drag new ling from output pin + // * input valid, output valid - user dragged link over other pin, can be validated + + if (inputPinId && outputPinId) // both are valid, let's accept link + { + // ed::AcceptNewItem() return true when user release mouse button. + if (ed::AcceptNewItem()) + { + // Since we accepted new link, lets add one to our list of links. + m_Links.push_back({ ed::LinkId(m_NextLinkId++), inputPinId, outputPinId }); + + // Draw new link. + ed::Link(m_Links.back().Id, m_Links.back().InputId, m_Links.back().OutputId); + } + + // You may choose to reject connection between these nodes + // by calling ed::RejectNewItem(). This will allow editor to give + // visual feedback by changing link thickness and color. + } + } + } + ed::EndCreate(); // Wraps up object creation action handling. + + + // Handle deletion action + if (ed::BeginDelete()) + { + // There may be many links marked for deletion, let's loop over them. + ed::LinkId deletedLinkId; + while (ed::QueryDeletedLink(&deletedLinkId)) + { + // If you agree that link can be deleted, accept deletion. + if (ed::AcceptDeletedItem()) + { + // Then remove link from your data. + for (auto& link : m_Links) + { + if (link.Id == deletedLinkId) + { + m_Links.erase(&link); + break; + } + } + } + + // You may reject link deletion by calling: + // ed::RejectDeletedItem(); + } + } + ed::EndDelete(); // Wrap up deletion action + + + + // End of interaction with editor. + ed::End(); + + if (m_FirstFrame) + ed::NavigateToContent(0.0f); + + ed::SetCurrentEditor(nullptr); + + m_FirstFrame = false; + + // ImGui::ShowMetricsWindow(); + } + + ed::EditorContext* m_Context = nullptr; // Editor context, required to trace a editor state. + bool m_FirstFrame = true; // Flag set for first frame only, some action need to be executed once. + ImVector m_Links; // List of live links. It is dynamic unless you want to create read-only view over nodes. + int m_NextLinkId = 100; // Counter to help generate link ids. In real application this will probably based on pointer to user data structure. +}; + +int Main(int argc, char** argv) +{ + Example exampe("Basic Interaction", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/CMakeLists.txt b/cpp/vendor/imgui-node-editor/examples/blueprints-example/CMakeLists.txt new file mode 100644 index 00000000..37ca1ec1 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/CMakeLists.txt @@ -0,0 +1,9 @@ +add_example_executable(blueprints-example + blueprints-example.cpp + utilities/builders.h + utilities/drawing.h + utilities/widgets.h + utilities/builders.cpp + utilities/drawing.cpp + utilities/widgets.cpp +) diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/blueprints-example.cpp b/cpp/vendor/imgui-node-editor/examples/blueprints-example/blueprints-example.cpp new file mode 100644 index 00000000..f72f3ad1 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/blueprints-example.cpp @@ -0,0 +1,1835 @@ +#define IMGUI_DEFINE_MATH_OPERATORS +#include +#include "utilities/builders.h" +#include "utilities/widgets.h" + +#include +#include + +#include +#include +#include +#include +#include + + +static inline ImRect ImGui_GetItemRect() +{ + return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); +} + +static inline ImRect ImRect_Expanded(const ImRect& rect, float x, float y) +{ + auto result = rect; + result.Min.x -= x; + result.Min.y -= y; + result.Max.x += x; + result.Max.y += y; + return result; +} + +namespace ed = ax::NodeEditor; +namespace util = ax::NodeEditor::Utilities; + +using namespace ax; + +using ax::Widgets::IconType; + +static ed::EditorContext* m_Editor = nullptr; + +//extern "C" __declspec(dllimport) short __stdcall GetAsyncKeyState(int vkey); +//extern "C" bool Debug_KeyPress(int vkey) +//{ +// static std::map state; +// auto lastState = state[vkey]; +// state[vkey] = (GetAsyncKeyState(vkey) & 0x8000) != 0; +// if (state[vkey] && !lastState) +// return true; +// else +// return false; +//} + +enum class PinType +{ + Flow, + Bool, + Int, + Float, + String, + Object, + Function, + Delegate, +}; + +enum class PinKind +{ + Output, + Input +}; + +enum class NodeType +{ + Blueprint, + Simple, + Tree, + Comment, + Houdini +}; + +struct Node; + +struct Pin +{ + ed::PinId ID; + ::Node* Node; + std::string Name; + PinType Type; + PinKind Kind; + + Pin(int id, const char* name, PinType type): + ID(id), Node(nullptr), Name(name), Type(type), Kind(PinKind::Input) + { + } +}; + +struct Node +{ + ed::NodeId ID; + std::string Name; + std::vector Inputs; + std::vector Outputs; + ImColor Color; + NodeType Type; + ImVec2 Size; + + std::string State; + std::string SavedState; + + Node(int id, const char* name, ImColor color = ImColor(255, 255, 255)): + ID(id), Name(name), Color(color), Type(NodeType::Blueprint), Size(0, 0) + { + } +}; + +struct Link +{ + ed::LinkId ID; + + ed::PinId StartPinID; + ed::PinId EndPinID; + + ImColor Color; + + Link(ed::LinkId id, ed::PinId startPinId, ed::PinId endPinId): + ID(id), StartPinID(startPinId), EndPinID(endPinId), Color(255, 255, 255) + { + } +}; + +struct NodeIdLess +{ + bool operator()(const ed::NodeId& lhs, const ed::NodeId& rhs) const + { + return lhs.AsPointer() < rhs.AsPointer(); + } +}; + +static bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f) +{ + using namespace ImGui; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); + return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f); +} + +struct Example: + public Application +{ + using Application::Application; + + int GetNextId() + { + return m_NextId++; + } + + //ed::NodeId GetNextNodeId() + //{ + // return ed::NodeId(GetNextId()); + //} + + ed::LinkId GetNextLinkId() + { + return ed::LinkId(GetNextId()); + } + + void TouchNode(ed::NodeId id) + { + m_NodeTouchTime[id] = m_TouchTime; + } + + float GetTouchProgress(ed::NodeId id) + { + auto it = m_NodeTouchTime.find(id); + if (it != m_NodeTouchTime.end() && it->second > 0.0f) + return (m_TouchTime - it->second) / m_TouchTime; + else + return 0.0f; + } + + void UpdateTouch() + { + const auto deltaTime = ImGui::GetIO().DeltaTime; + for (auto& entry : m_NodeTouchTime) + { + if (entry.second > 0.0f) + entry.second -= deltaTime; + } + } + + Node* FindNode(ed::NodeId id) + { + for (auto& node : m_Nodes) + if (node.ID == id) + return &node; + + return nullptr; + } + + Link* FindLink(ed::LinkId id) + { + for (auto& link : m_Links) + if (link.ID == id) + return &link; + + return nullptr; + } + + Pin* FindPin(ed::PinId id) + { + if (!id) + return nullptr; + + for (auto& node : m_Nodes) + { + for (auto& pin : node.Inputs) + if (pin.ID == id) + return &pin; + + for (auto& pin : node.Outputs) + if (pin.ID == id) + return &pin; + } + + return nullptr; + } + + bool IsPinLinked(ed::PinId id) + { + if (!id) + return false; + + for (auto& link : m_Links) + if (link.StartPinID == id || link.EndPinID == id) + return true; + + return false; + } + + bool CanCreateLink(Pin* a, Pin* b) + { + if (!a || !b || a == b || a->Kind == b->Kind || a->Type != b->Type || a->Node == b->Node) + return false; + + return true; + } + + //void DrawItemRect(ImColor color, float expand = 0.0f) + //{ + // ImGui::GetWindowDrawList()->AddRect( + // ImGui::GetItemRectMin() - ImVec2(expand, expand), + // ImGui::GetItemRectMax() + ImVec2(expand, expand), + // color); + //}; + + //void FillItemRect(ImColor color, float expand = 0.0f, float rounding = 0.0f) + //{ + // ImGui::GetWindowDrawList()->AddRectFilled( + // ImGui::GetItemRectMin() - ImVec2(expand, expand), + // ImGui::GetItemRectMax() + ImVec2(expand, expand), + // color, rounding); + //}; + + void BuildNode(Node* node) + { + for (auto& input : node->Inputs) + { + input.Node = node; + input.Kind = PinKind::Input; + } + + for (auto& output : node->Outputs) + { + output.Node = node; + output.Kind = PinKind::Output; + } + } + + Node* SpawnInputActionNode() + { + m_Nodes.emplace_back(GetNextId(), "InputAction Fire", ImColor(255, 128, 128)); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Delegate); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Pressed", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Released", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnBranchNode() + { + m_Nodes.emplace_back(GetNextId(), "Branch"); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Condition", PinType::Bool); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "True", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "False", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnDoNNode() + { + m_Nodes.emplace_back(GetNextId(), "Do N"); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Enter", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "N", PinType::Int); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Reset", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Exit", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Counter", PinType::Int); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnOutputActionNode() + { + m_Nodes.emplace_back(GetNextId(), "OutputAction"); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Sample", PinType::Float); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Condition", PinType::Bool); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Event", PinType::Delegate); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnPrintStringNode() + { + m_Nodes.emplace_back(GetNextId(), "Print String"); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "In String", PinType::String); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnMessageNode() + { + m_Nodes.emplace_back(GetNextId(), "", ImColor(128, 195, 248)); + m_Nodes.back().Type = NodeType::Simple; + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Message", PinType::String); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnSetTimerNode() + { + m_Nodes.emplace_back(GetNextId(), "Set Timer", ImColor(128, 195, 248)); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Object", PinType::Object); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Function Name", PinType::Function); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Time", PinType::Float); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Looping", PinType::Bool); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnLessNode() + { + m_Nodes.emplace_back(GetNextId(), "<", ImColor(128, 195, 248)); + m_Nodes.back().Type = NodeType::Simple; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Float); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Float); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Float); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnWeirdNode() + { + m_Nodes.emplace_back(GetNextId(), "o.O", ImColor(128, 195, 248)); + m_Nodes.back().Type = NodeType::Simple; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Float); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Float); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Float); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnTraceByChannelNode() + { + m_Nodes.emplace_back(GetNextId(), "Single Line Trace by Channel", ImColor(255, 128, 64)); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Start", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "End", PinType::Int); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Trace Channel", PinType::Float); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Trace Complex", PinType::Bool); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Actors to Ignore", PinType::Int); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Draw Debug Type", PinType::Bool); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "Ignore Self", PinType::Bool); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Out Hit", PinType::Float); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "Return Value", PinType::Bool); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnTreeSequenceNode() + { + m_Nodes.emplace_back(GetNextId(), "Sequence"); + m_Nodes.back().Type = NodeType::Tree; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnTreeTaskNode() + { + m_Nodes.emplace_back(GetNextId(), "Move To"); + m_Nodes.back().Type = NodeType::Tree; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnTreeTask2Node() + { + m_Nodes.emplace_back(GetNextId(), "Random Wait"); + m_Nodes.back().Type = NodeType::Tree; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnComment() + { + m_Nodes.emplace_back(GetNextId(), "Test Comment"); + m_Nodes.back().Type = NodeType::Comment; + m_Nodes.back().Size = ImVec2(300, 200); + + return &m_Nodes.back(); + } + + Node* SpawnHoudiniTransformNode() + { + m_Nodes.emplace_back(GetNextId(), "Transform"); + m_Nodes.back().Type = NodeType::Houdini; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + Node* SpawnHoudiniGroupNode() + { + m_Nodes.emplace_back(GetNextId(), "Group"); + m_Nodes.back().Type = NodeType::Houdini; + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Inputs.emplace_back(GetNextId(), "", PinType::Flow); + m_Nodes.back().Outputs.emplace_back(GetNextId(), "", PinType::Flow); + + BuildNode(&m_Nodes.back()); + + return &m_Nodes.back(); + } + + void BuildNodes() + { + for (auto& node : m_Nodes) + BuildNode(&node); + } + + void OnStart() override + { + ed::Config config; + + config.SettingsFile = "Blueprints.json"; + + config.UserPointer = this; + + config.LoadNodeSettings = [](ed::NodeId nodeId, char* data, void* userPointer) -> size_t + { + auto self = static_cast(userPointer); + + auto node = self->FindNode(nodeId); + if (!node) + return 0; + + if (data != nullptr) + memcpy(data, node->State.data(), node->State.size()); + return node->State.size(); + }; + + config.SaveNodeSettings = [](ed::NodeId nodeId, const char* data, size_t size, ed::SaveReasonFlags reason, void* userPointer) -> bool + { + auto self = static_cast(userPointer); + + auto node = self->FindNode(nodeId); + if (!node) + return false; + + node->State.assign(data, size); + + self->TouchNode(nodeId); + + return true; + }; + + m_Editor = ed::CreateEditor(&config); + ed::SetCurrentEditor(m_Editor); + + Node* node; + node = SpawnInputActionNode(); ed::SetNodePosition(node->ID, ImVec2(-252, 220)); + node = SpawnBranchNode(); ed::SetNodePosition(node->ID, ImVec2(-300, 351)); + node = SpawnDoNNode(); ed::SetNodePosition(node->ID, ImVec2(-238, 504)); + node = SpawnOutputActionNode(); ed::SetNodePosition(node->ID, ImVec2(71, 80)); + node = SpawnSetTimerNode(); ed::SetNodePosition(node->ID, ImVec2(168, 316)); + + node = SpawnTreeSequenceNode(); ed::SetNodePosition(node->ID, ImVec2(1028, 329)); + node = SpawnTreeTaskNode(); ed::SetNodePosition(node->ID, ImVec2(1204, 458)); + node = SpawnTreeTask2Node(); ed::SetNodePosition(node->ID, ImVec2(868, 538)); + + node = SpawnComment(); ed::SetNodePosition(node->ID, ImVec2(112, 576)); ed::SetGroupSize(node->ID, ImVec2(384, 154)); + node = SpawnComment(); ed::SetNodePosition(node->ID, ImVec2(800, 224)); ed::SetGroupSize(node->ID, ImVec2(640, 400)); + + node = SpawnLessNode(); ed::SetNodePosition(node->ID, ImVec2(366, 652)); + node = SpawnWeirdNode(); ed::SetNodePosition(node->ID, ImVec2(144, 652)); + node = SpawnMessageNode(); ed::SetNodePosition(node->ID, ImVec2(-348, 698)); + node = SpawnPrintStringNode(); ed::SetNodePosition(node->ID, ImVec2(-69, 652)); + + node = SpawnHoudiniTransformNode(); ed::SetNodePosition(node->ID, ImVec2(500, -70)); + node = SpawnHoudiniGroupNode(); ed::SetNodePosition(node->ID, ImVec2(500, 42)); + + ed::NavigateToContent(); + + BuildNodes(); + + m_Links.push_back(Link(GetNextLinkId(), m_Nodes[5].Outputs[0].ID, m_Nodes[6].Inputs[0].ID)); + m_Links.push_back(Link(GetNextLinkId(), m_Nodes[5].Outputs[0].ID, m_Nodes[7].Inputs[0].ID)); + + m_Links.push_back(Link(GetNextLinkId(), m_Nodes[14].Outputs[0].ID, m_Nodes[15].Inputs[0].ID)); + + m_HeaderBackground = LoadTexture("data/BlueprintBackground.png"); + m_SaveIcon = LoadTexture("data/ic_save_white_24dp.png"); + m_RestoreIcon = LoadTexture("data/ic_restore_white_24dp.png"); + + + //auto& io = ImGui::GetIO(); + } + + void OnStop() override + { + auto releaseTexture = [this](ImTextureID& id) + { + if (id) + { + DestroyTexture(id); + id = nullptr; + } + }; + + releaseTexture(m_RestoreIcon); + releaseTexture(m_SaveIcon); + releaseTexture(m_HeaderBackground); + + if (m_Editor) + { + ed::DestroyEditor(m_Editor); + m_Editor = nullptr; + } + } + + ImColor GetIconColor(PinType type) + { + switch (type) + { + default: + case PinType::Flow: return ImColor(255, 255, 255); + case PinType::Bool: return ImColor(220, 48, 48); + case PinType::Int: return ImColor( 68, 201, 156); + case PinType::Float: return ImColor(147, 226, 74); + case PinType::String: return ImColor(124, 21, 153); + case PinType::Object: return ImColor( 51, 150, 215); + case PinType::Function: return ImColor(218, 0, 183); + case PinType::Delegate: return ImColor(255, 48, 48); + } + }; + + void DrawPinIcon(const Pin& pin, bool connected, int alpha) + { + IconType iconType; + ImColor color = GetIconColor(pin.Type); + color.Value.w = alpha / 255.0f; + switch (pin.Type) + { + case PinType::Flow: iconType = IconType::Flow; break; + case PinType::Bool: iconType = IconType::Circle; break; + case PinType::Int: iconType = IconType::Circle; break; + case PinType::Float: iconType = IconType::Circle; break; + case PinType::String: iconType = IconType::Circle; break; + case PinType::Object: iconType = IconType::Circle; break; + case PinType::Function: iconType = IconType::Circle; break; + case PinType::Delegate: iconType = IconType::Square; break; + default: + return; + } + + ax::Widgets::Icon(ImVec2(static_cast(m_PinIconSize), static_cast(m_PinIconSize)), iconType, connected, color, ImColor(32, 32, 32, alpha)); + }; + + void ShowStyleEditor(bool* show = nullptr) + { + if (!ImGui::Begin("Style", show)) + { + ImGui::End(); + return; + } + + auto paneWidth = ImGui::GetContentRegionAvail().x; + + auto& editorStyle = ed::GetStyle(); + ImGui::BeginHorizontal("Style buttons", ImVec2(paneWidth, 0), 1.0f); + ImGui::TextUnformatted("Values"); + ImGui::Spring(); + if (ImGui::Button("Reset to defaults")) + editorStyle = ed::Style(); + ImGui::EndHorizontal(); + ImGui::Spacing(); + ImGui::DragFloat4("Node Padding", &editorStyle.NodePadding.x, 0.1f, 0.0f, 40.0f); + ImGui::DragFloat("Node Rounding", &editorStyle.NodeRounding, 0.1f, 0.0f, 40.0f); + ImGui::DragFloat("Node Border Width", &editorStyle.NodeBorderWidth, 0.1f, 0.0f, 15.0f); + ImGui::DragFloat("Hovered Node Border Width", &editorStyle.HoveredNodeBorderWidth, 0.1f, 0.0f, 15.0f); + ImGui::DragFloat("Hovered Node Border Offset", &editorStyle.HoverNodeBorderOffset, 0.1f, -40.0f, 40.0f); + ImGui::DragFloat("Selected Node Border Width", &editorStyle.SelectedNodeBorderWidth, 0.1f, 0.0f, 15.0f); + ImGui::DragFloat("Selected Node Border Offset", &editorStyle.SelectedNodeBorderOffset, 0.1f, -40.0f, 40.0f); + ImGui::DragFloat("Pin Rounding", &editorStyle.PinRounding, 0.1f, 0.0f, 40.0f); + ImGui::DragFloat("Pin Border Width", &editorStyle.PinBorderWidth, 0.1f, 0.0f, 15.0f); + ImGui::DragFloat("Link Strength", &editorStyle.LinkStrength, 1.0f, 0.0f, 500.0f); + //ImVec2 SourceDirection; + //ImVec2 TargetDirection; + ImGui::DragFloat("Scroll Duration", &editorStyle.ScrollDuration, 0.001f, 0.0f, 2.0f); + ImGui::DragFloat("Flow Marker Distance", &editorStyle.FlowMarkerDistance, 1.0f, 1.0f, 200.0f); + ImGui::DragFloat("Flow Speed", &editorStyle.FlowSpeed, 1.0f, 1.0f, 2000.0f); + ImGui::DragFloat("Flow Duration", &editorStyle.FlowDuration, 0.001f, 0.0f, 5.0f); + //ImVec2 PivotAlignment; + //ImVec2 PivotSize; + //ImVec2 PivotScale; + //float PinCorners; + //float PinRadius; + //float PinArrowSize; + //float PinArrowWidth; + ImGui::DragFloat("Group Rounding", &editorStyle.GroupRounding, 0.1f, 0.0f, 40.0f); + ImGui::DragFloat("Group Border Width", &editorStyle.GroupBorderWidth, 0.1f, 0.0f, 15.0f); + + ImGui::Separator(); + + static ImGuiColorEditFlags edit_mode = ImGuiColorEditFlags_DisplayRGB; + ImGui::BeginHorizontal("Color Mode", ImVec2(paneWidth, 0), 1.0f); + ImGui::TextUnformatted("Filter Colors"); + ImGui::Spring(); + ImGui::RadioButton("RGB", &edit_mode, ImGuiColorEditFlags_DisplayRGB); + ImGui::Spring(0); + ImGui::RadioButton("HSV", &edit_mode, ImGuiColorEditFlags_DisplayHSV); + ImGui::Spring(0); + ImGui::RadioButton("HEX", &edit_mode, ImGuiColorEditFlags_DisplayHex); + ImGui::EndHorizontal(); + + static ImGuiTextFilter filter; + filter.Draw("##filter", paneWidth); + + ImGui::Spacing(); + + ImGui::PushItemWidth(-160); + for (int i = 0; i < ed::StyleColor_Count; ++i) + { + auto name = ed::GetStyleColorName((ed::StyleColor)i); + if (!filter.PassFilter(name)) + continue; + + ImGui::ColorEdit4(name, &editorStyle.Colors[i].x, edit_mode); + } + ImGui::PopItemWidth(); + + ImGui::End(); + } + + void ShowLeftPane(float paneWidth) + { + auto& io = ImGui::GetIO(); + + ImGui::BeginChild("Selection", ImVec2(paneWidth, 0)); + + paneWidth = ImGui::GetContentRegionAvail().x; + + static bool showStyleEditor = false; + ImGui::BeginHorizontal("Style Editor", ImVec2(paneWidth, 0)); + ImGui::Spring(0.0f, 0.0f); + if (ImGui::Button("Zoom to Content")) + ed::NavigateToContent(); + ImGui::Spring(0.0f); + if (ImGui::Button("Show Flow")) + { + for (auto& link : m_Links) + ed::Flow(link.ID); + } + ImGui::Spring(); + if (ImGui::Button("Edit Style")) + showStyleEditor = true; + ImGui::EndHorizontal(); + ImGui::Checkbox("Show Ordinals", &m_ShowOrdinals); + + if (showStyleEditor) + ShowStyleEditor(&showStyleEditor); + + std::vector selectedNodes; + std::vector selectedLinks; + selectedNodes.resize(ed::GetSelectedObjectCount()); + selectedLinks.resize(ed::GetSelectedObjectCount()); + + int nodeCount = ed::GetSelectedNodes(selectedNodes.data(), static_cast(selectedNodes.size())); + int linkCount = ed::GetSelectedLinks(selectedLinks.data(), static_cast(selectedLinks.size())); + + selectedNodes.resize(nodeCount); + selectedLinks.resize(linkCount); + + int saveIconWidth = GetTextureWidth(m_SaveIcon); + int saveIconHeight = GetTextureWidth(m_SaveIcon); + int restoreIconWidth = GetTextureWidth(m_RestoreIcon); + int restoreIconHeight = GetTextureWidth(m_RestoreIcon); + + ImGui::GetWindowDrawList()->AddRectFilled( + ImGui::GetCursorScreenPos(), + ImGui::GetCursorScreenPos() + ImVec2(paneWidth, ImGui::GetTextLineHeight()), + ImColor(ImGui::GetStyle().Colors[ImGuiCol_HeaderActive]), ImGui::GetTextLineHeight() * 0.25f); + ImGui::Spacing(); ImGui::SameLine(); + ImGui::TextUnformatted("Nodes"); + ImGui::Indent(); + for (auto& node : m_Nodes) + { + ImGui::PushID(node.ID.AsPointer()); + auto start = ImGui::GetCursorScreenPos(); + + if (const auto progress = GetTouchProgress(node.ID)) + { + ImGui::GetWindowDrawList()->AddLine( + start + ImVec2(-8, 0), + start + ImVec2(-8, ImGui::GetTextLineHeight()), + IM_COL32(255, 0, 0, 255 - (int)(255 * progress)), 4.0f); + } + + bool isSelected = std::find(selectedNodes.begin(), selectedNodes.end(), node.ID) != selectedNodes.end(); +# if IMGUI_VERSION_NUM >= 18967 + ImGui::SetNextItemAllowOverlap(); +# endif + if (ImGui::Selectable((node.Name + "##" + std::to_string(reinterpret_cast(node.ID.AsPointer()))).c_str(), &isSelected)) + { + if (io.KeyCtrl) + { + if (isSelected) + ed::SelectNode(node.ID, true); + else + ed::DeselectNode(node.ID); + } + else + ed::SelectNode(node.ID, false); + + ed::NavigateToSelection(); + } + if (ImGui::IsItemHovered() && !node.State.empty()) + ImGui::SetTooltip("State: %s", node.State.c_str()); + + auto id = std::string("(") + std::to_string(reinterpret_cast(node.ID.AsPointer())) + ")"; + auto textSize = ImGui::CalcTextSize(id.c_str(), nullptr); + auto iconPanelPos = start + ImVec2( + paneWidth - ImGui::GetStyle().FramePadding.x - ImGui::GetStyle().IndentSpacing - saveIconWidth - restoreIconWidth - ImGui::GetStyle().ItemInnerSpacing.x * 1, + (ImGui::GetTextLineHeight() - saveIconHeight) / 2); + ImGui::GetWindowDrawList()->AddText( + ImVec2(iconPanelPos.x - textSize.x - ImGui::GetStyle().ItemInnerSpacing.x, start.y), + IM_COL32(255, 255, 255, 255), id.c_str(), nullptr); + + auto drawList = ImGui::GetWindowDrawList(); + ImGui::SetCursorScreenPos(iconPanelPos); +# if IMGUI_VERSION_NUM < 18967 + ImGui::SetItemAllowOverlap(); +# else + ImGui::SetNextItemAllowOverlap(); +# endif + if (node.SavedState.empty()) + { + if (ImGui::InvisibleButton("save", ImVec2((float)saveIconWidth, (float)saveIconHeight))) + node.SavedState = node.State; + + if (ImGui::IsItemActive()) + drawList->AddImage(m_SaveIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 96)); + else if (ImGui::IsItemHovered()) + drawList->AddImage(m_SaveIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 255)); + else + drawList->AddImage(m_SaveIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 160)); + } + else + { + ImGui::Dummy(ImVec2((float)saveIconWidth, (float)saveIconHeight)); + drawList->AddImage(m_SaveIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 32)); + } + + ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); +# if IMGUI_VERSION_NUM < 18967 + ImGui::SetItemAllowOverlap(); +# else + ImGui::SetNextItemAllowOverlap(); +# endif + if (!node.SavedState.empty()) + { + if (ImGui::InvisibleButton("restore", ImVec2((float)restoreIconWidth, (float)restoreIconHeight))) + { + node.State = node.SavedState; + ed::RestoreNodeState(node.ID); + node.SavedState.clear(); + } + + if (ImGui::IsItemActive()) + drawList->AddImage(m_RestoreIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 96)); + else if (ImGui::IsItemHovered()) + drawList->AddImage(m_RestoreIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 255)); + else + drawList->AddImage(m_RestoreIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 160)); + } + else + { + ImGui::Dummy(ImVec2((float)restoreIconWidth, (float)restoreIconHeight)); + drawList->AddImage(m_RestoreIcon, ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, 32)); + } + + ImGui::SameLine(0, 0); +# if IMGUI_VERSION_NUM < 18967 + ImGui::SetItemAllowOverlap(); +# endif + ImGui::Dummy(ImVec2(0, (float)restoreIconHeight)); + + ImGui::PopID(); + } + ImGui::Unindent(); + + static int changeCount = 0; + + ImGui::GetWindowDrawList()->AddRectFilled( + ImGui::GetCursorScreenPos(), + ImGui::GetCursorScreenPos() + ImVec2(paneWidth, ImGui::GetTextLineHeight()), + ImColor(ImGui::GetStyle().Colors[ImGuiCol_HeaderActive]), ImGui::GetTextLineHeight() * 0.25f); + ImGui::Spacing(); ImGui::SameLine(); + ImGui::TextUnformatted("Selection"); + + ImGui::BeginHorizontal("Selection Stats", ImVec2(paneWidth, 0)); + ImGui::Text("Changed %d time%s", changeCount, changeCount > 1 ? "s" : ""); + ImGui::Spring(); + if (ImGui::Button("Deselect All")) + ed::ClearSelection(); + ImGui::EndHorizontal(); + ImGui::Indent(); + for (int i = 0; i < nodeCount; ++i) ImGui::Text("Node (%p)", selectedNodes[i].AsPointer()); + for (int i = 0; i < linkCount; ++i) ImGui::Text("Link (%p)", selectedLinks[i].AsPointer()); + ImGui::Unindent(); + + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + for (auto& link : m_Links) + ed::Flow(link.ID); + + if (ed::HasSelectionChanged()) + ++changeCount; + + ImGui::EndChild(); + } + + void OnFrame(float deltaTime) override + { + UpdateTouch(); + + auto& io = ImGui::GetIO(); + + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + + ed::SetCurrentEditor(m_Editor); + + //auto& style = ImGui::GetStyle(); + + # if 0 + { + for (auto x = -io.DisplaySize.y; x < io.DisplaySize.x; x += 10.0f) + { + ImGui::GetWindowDrawList()->AddLine(ImVec2(x, 0), ImVec2(x + io.DisplaySize.y, io.DisplaySize.y), + IM_COL32(255, 255, 0, 255)); + } + } + # endif + + static ed::NodeId contextNodeId = 0; + static ed::LinkId contextLinkId = 0; + static ed::PinId contextPinId = 0; + static bool createNewNode = false; + static Pin* newNodeLinkPin = nullptr; + static Pin* newLinkPin = nullptr; + + static float leftPaneWidth = 400.0f; + static float rightPaneWidth = 800.0f; + Splitter(true, 4.0f, &leftPaneWidth, &rightPaneWidth, 50.0f, 50.0f); + + ShowLeftPane(leftPaneWidth - 4.0f); + + ImGui::SameLine(0.0f, 12.0f); + + ed::Begin("Node editor"); + { + auto cursorTopLeft = ImGui::GetCursorScreenPos(); + + util::BlueprintNodeBuilder builder(m_HeaderBackground, GetTextureWidth(m_HeaderBackground), GetTextureHeight(m_HeaderBackground)); + + for (auto& node : m_Nodes) + { + if (node.Type != NodeType::Blueprint && node.Type != NodeType::Simple) + continue; + + const auto isSimple = node.Type == NodeType::Simple; + + bool hasOutputDelegates = false; + for (auto& output : node.Outputs) + if (output.Type == PinType::Delegate) + hasOutputDelegates = true; + + builder.Begin(node.ID); + if (!isSimple) + { + builder.Header(node.Color); + ImGui::Spring(0); + ImGui::TextUnformatted(node.Name.c_str()); + ImGui::Spring(1); + ImGui::Dummy(ImVec2(0, 28)); + if (hasOutputDelegates) + { + ImGui::BeginVertical("delegates", ImVec2(0, 28)); + ImGui::Spring(1, 0); + for (auto& output : node.Outputs) + { + if (output.Type != PinType::Delegate) + continue; + + auto alpha = ImGui::GetStyle().Alpha; + if (newLinkPin && !CanCreateLink(newLinkPin, &output) && &output != newLinkPin) + alpha = alpha * (48.0f / 255.0f); + + ed::BeginPin(output.ID, ed::PinKind::Output); + ed::PinPivotAlignment(ImVec2(1.0f, 0.5f)); + ed::PinPivotSize(ImVec2(0, 0)); + ImGui::BeginHorizontal(output.ID.AsPointer()); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + if (!output.Name.empty()) + { + ImGui::TextUnformatted(output.Name.c_str()); + ImGui::Spring(0); + } + DrawPinIcon(output, IsPinLinked(output.ID), (int)(alpha * 255)); + ImGui::Spring(0, ImGui::GetStyle().ItemSpacing.x / 2); + ImGui::EndHorizontal(); + ImGui::PopStyleVar(); + ed::EndPin(); + + //DrawItemRect(ImColor(255, 0, 0)); + } + ImGui::Spring(1, 0); + ImGui::EndVertical(); + ImGui::Spring(0, ImGui::GetStyle().ItemSpacing.x / 2); + } + else + ImGui::Spring(0); + builder.EndHeader(); + } + + for (auto& input : node.Inputs) + { + auto alpha = ImGui::GetStyle().Alpha; + if (newLinkPin && !CanCreateLink(newLinkPin, &input) && &input != newLinkPin) + alpha = alpha * (48.0f / 255.0f); + + builder.Input(input.ID); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + DrawPinIcon(input, IsPinLinked(input.ID), (int)(alpha * 255)); + ImGui::Spring(0); + if (!input.Name.empty()) + { + ImGui::TextUnformatted(input.Name.c_str()); + ImGui::Spring(0); + } + if (input.Type == PinType::Bool) + { + ImGui::Button("Hello"); + ImGui::Spring(0); + } + ImGui::PopStyleVar(); + builder.EndInput(); + } + + if (isSimple) + { + builder.Middle(); + + ImGui::Spring(1, 0); + ImGui::TextUnformatted(node.Name.c_str()); + ImGui::Spring(1, 0); + } + + for (auto& output : node.Outputs) + { + if (!isSimple && output.Type == PinType::Delegate) + continue; + + auto alpha = ImGui::GetStyle().Alpha; + if (newLinkPin && !CanCreateLink(newLinkPin, &output) && &output != newLinkPin) + alpha = alpha * (48.0f / 255.0f); + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + builder.Output(output.ID); + if (output.Type == PinType::String) + { + static char buffer[128] = "Edit Me\nMultiline!"; + static bool wasActive = false; + + ImGui::PushItemWidth(100.0f); + ImGui::InputText("##edit", buffer, 127); + ImGui::PopItemWidth(); + if (ImGui::IsItemActive() && !wasActive) + { + ed::EnableShortcuts(false); + wasActive = true; + } + else if (!ImGui::IsItemActive() && wasActive) + { + ed::EnableShortcuts(true); + wasActive = false; + } + ImGui::Spring(0); + } + if (!output.Name.empty()) + { + ImGui::Spring(0); + ImGui::TextUnformatted(output.Name.c_str()); + } + ImGui::Spring(0); + DrawPinIcon(output, IsPinLinked(output.ID), (int)(alpha * 255)); + ImGui::PopStyleVar(); + builder.EndOutput(); + } + + builder.End(); + } + + for (auto& node : m_Nodes) + { + if (node.Type != NodeType::Tree) + continue; + + const float rounding = 5.0f; + const float padding = 12.0f; + + const auto pinBackground = ed::GetStyle().Colors[ed::StyleColor_NodeBg]; + + ed::PushStyleColor(ed::StyleColor_NodeBg, ImColor(128, 128, 128, 200)); + ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor( 32, 32, 32, 200)); + ed::PushStyleColor(ed::StyleColor_PinRect, ImColor( 60, 180, 255, 150)); + ed::PushStyleColor(ed::StyleColor_PinRectBorder, ImColor( 60, 180, 255, 150)); + + ed::PushStyleVar(ed::StyleVar_NodePadding, ImVec4(0, 0, 0, 0)); + ed::PushStyleVar(ed::StyleVar_NodeRounding, rounding); + ed::PushStyleVar(ed::StyleVar_SourceDirection, ImVec2(0.0f, 1.0f)); + ed::PushStyleVar(ed::StyleVar_TargetDirection, ImVec2(0.0f, -1.0f)); + ed::PushStyleVar(ed::StyleVar_LinkStrength, 0.0f); + ed::PushStyleVar(ed::StyleVar_PinBorderWidth, 1.0f); + ed::PushStyleVar(ed::StyleVar_PinRadius, 5.0f); + ed::BeginNode(node.ID); + + ImGui::BeginVertical(node.ID.AsPointer()); + ImGui::BeginHorizontal("inputs"); + ImGui::Spring(0, padding * 2); + + ImRect inputsRect; + int inputAlpha = 200; + if (!node.Inputs.empty()) + { + auto& pin = node.Inputs[0]; + ImGui::Dummy(ImVec2(0, padding)); + ImGui::Spring(1, 0); + inputsRect = ImGui_GetItemRect(); + + ed::PushStyleVar(ed::StyleVar_PinArrowSize, 10.0f); + ed::PushStyleVar(ed::StyleVar_PinArrowWidth, 10.0f); +#if IMGUI_VERSION_NUM > 18101 + ed::PushStyleVar(ed::StyleVar_PinCorners, ImDrawFlags_RoundCornersBottom); +#else + ed::PushStyleVar(ed::StyleVar_PinCorners, 12); +#endif + ed::BeginPin(pin.ID, ed::PinKind::Input); + ed::PinPivotRect(inputsRect.GetTL(), inputsRect.GetBR()); + ed::PinRect(inputsRect.GetTL(), inputsRect.GetBR()); + ed::EndPin(); + ed::PopStyleVar(3); + + if (newLinkPin && !CanCreateLink(newLinkPin, &pin) && &pin != newLinkPin) + inputAlpha = (int)(255 * ImGui::GetStyle().Alpha * (48.0f / 255.0f)); + } + else + ImGui::Dummy(ImVec2(0, padding)); + + ImGui::Spring(0, padding * 2); + ImGui::EndHorizontal(); + + ImGui::BeginHorizontal("content_frame"); + ImGui::Spring(1, padding); + + ImGui::BeginVertical("content", ImVec2(0.0f, 0.0f)); + ImGui::Dummy(ImVec2(160, 0)); + ImGui::Spring(1); + ImGui::TextUnformatted(node.Name.c_str()); + ImGui::Spring(1); + ImGui::EndVertical(); + auto contentRect = ImGui_GetItemRect(); + + ImGui::Spring(1, padding); + ImGui::EndHorizontal(); + + ImGui::BeginHorizontal("outputs"); + ImGui::Spring(0, padding * 2); + + ImRect outputsRect; + int outputAlpha = 200; + if (!node.Outputs.empty()) + { + auto& pin = node.Outputs[0]; + ImGui::Dummy(ImVec2(0, padding)); + ImGui::Spring(1, 0); + outputsRect = ImGui_GetItemRect(); + +#if IMGUI_VERSION_NUM > 18101 + ed::PushStyleVar(ed::StyleVar_PinCorners, ImDrawFlags_RoundCornersTop); +#else + ed::PushStyleVar(ed::StyleVar_PinCorners, 3); +#endif + ed::BeginPin(pin.ID, ed::PinKind::Output); + ed::PinPivotRect(outputsRect.GetTL(), outputsRect.GetBR()); + ed::PinRect(outputsRect.GetTL(), outputsRect.GetBR()); + ed::EndPin(); + ed::PopStyleVar(); + + if (newLinkPin && !CanCreateLink(newLinkPin, &pin) && &pin != newLinkPin) + outputAlpha = (int)(255 * ImGui::GetStyle().Alpha * (48.0f / 255.0f)); + } + else + ImGui::Dummy(ImVec2(0, padding)); + + ImGui::Spring(0, padding * 2); + ImGui::EndHorizontal(); + + ImGui::EndVertical(); + + ed::EndNode(); + ed::PopStyleVar(7); + ed::PopStyleColor(4); + + auto drawList = ed::GetNodeBackgroundDrawList(node.ID); + + //const auto fringeScale = ImGui::GetStyle().AntiAliasFringeScale; + //const auto unitSize = 1.0f / fringeScale; + + //const auto ImDrawList_AddRect = [](ImDrawList* drawList, const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners, float thickness) + //{ + // if ((col >> 24) == 0) + // return; + // drawList->PathRect(a, b, rounding, rounding_corners); + // drawList->PathStroke(col, true, thickness); + //}; + +#if IMGUI_VERSION_NUM > 18101 + const auto topRoundCornersFlags = ImDrawFlags_RoundCornersTop; + const auto bottomRoundCornersFlags = ImDrawFlags_RoundCornersBottom; +#else + const auto topRoundCornersFlags = 1 | 2; + const auto bottomRoundCornersFlags = 4 | 8; +#endif + + drawList->AddRectFilled(inputsRect.GetTL() + ImVec2(0, 1), inputsRect.GetBR(), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), inputAlpha), 4.0f, bottomRoundCornersFlags); + //ImGui::PushStyleVar(ImGuiStyleVar_AntiAliasFringeScale, 1.0f); + drawList->AddRect(inputsRect.GetTL() + ImVec2(0, 1), inputsRect.GetBR(), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), inputAlpha), 4.0f, bottomRoundCornersFlags); + //ImGui::PopStyleVar(); + drawList->AddRectFilled(outputsRect.GetTL(), outputsRect.GetBR() - ImVec2(0, 1), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), outputAlpha), 4.0f, topRoundCornersFlags); + //ImGui::PushStyleVar(ImGuiStyleVar_AntiAliasFringeScale, 1.0f); + drawList->AddRect(outputsRect.GetTL(), outputsRect.GetBR() - ImVec2(0, 1), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), outputAlpha), 4.0f, topRoundCornersFlags); + //ImGui::PopStyleVar(); + drawList->AddRectFilled(contentRect.GetTL(), contentRect.GetBR(), IM_COL32(24, 64, 128, 200), 0.0f); + //ImGui::PushStyleVar(ImGuiStyleVar_AntiAliasFringeScale, 1.0f); + drawList->AddRect( + contentRect.GetTL(), + contentRect.GetBR(), + IM_COL32(48, 128, 255, 100), 0.0f); + //ImGui::PopStyleVar(); + } + + for (auto& node : m_Nodes) + { + if (node.Type != NodeType::Houdini) + continue; + + const float rounding = 10.0f; + const float padding = 12.0f; + + + ed::PushStyleColor(ed::StyleColor_NodeBg, ImColor(229, 229, 229, 200)); + ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(125, 125, 125, 200)); + ed::PushStyleColor(ed::StyleColor_PinRect, ImColor(229, 229, 229, 60)); + ed::PushStyleColor(ed::StyleColor_PinRectBorder, ImColor(125, 125, 125, 60)); + + const auto pinBackground = ed::GetStyle().Colors[ed::StyleColor_NodeBg]; + + ed::PushStyleVar(ed::StyleVar_NodePadding, ImVec4(0, 0, 0, 0)); + ed::PushStyleVar(ed::StyleVar_NodeRounding, rounding); + ed::PushStyleVar(ed::StyleVar_SourceDirection, ImVec2(0.0f, 1.0f)); + ed::PushStyleVar(ed::StyleVar_TargetDirection, ImVec2(0.0f, -1.0f)); + ed::PushStyleVar(ed::StyleVar_LinkStrength, 0.0f); + ed::PushStyleVar(ed::StyleVar_PinBorderWidth, 1.0f); + ed::PushStyleVar(ed::StyleVar_PinRadius, 6.0f); + ed::BeginNode(node.ID); + + ImGui::BeginVertical(node.ID.AsPointer()); + if (!node.Inputs.empty()) + { + ImGui::BeginHorizontal("inputs"); + ImGui::Spring(1, 0); + + ImRect inputsRect; + int inputAlpha = 200; + for (auto& pin : node.Inputs) + { + ImGui::Dummy(ImVec2(padding, padding)); + inputsRect = ImGui_GetItemRect(); + ImGui::Spring(1, 0); + inputsRect.Min.y -= padding; + inputsRect.Max.y -= padding; + +#if IMGUI_VERSION_NUM > 18101 + const auto allRoundCornersFlags = ImDrawFlags_RoundCornersAll; +#else + const auto allRoundCornersFlags = 15; +#endif + //ed::PushStyleVar(ed::StyleVar_PinArrowSize, 10.0f); + //ed::PushStyleVar(ed::StyleVar_PinArrowWidth, 10.0f); + ed::PushStyleVar(ed::StyleVar_PinCorners, allRoundCornersFlags); + + ed::BeginPin(pin.ID, ed::PinKind::Input); + ed::PinPivotRect(inputsRect.GetCenter(), inputsRect.GetCenter()); + ed::PinRect(inputsRect.GetTL(), inputsRect.GetBR()); + ed::EndPin(); + //ed::PopStyleVar(3); + ed::PopStyleVar(1); + + auto drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(inputsRect.GetTL(), inputsRect.GetBR(), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), inputAlpha), 4.0f, allRoundCornersFlags); + drawList->AddRect(inputsRect.GetTL(), inputsRect.GetBR(), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), inputAlpha), 4.0f, allRoundCornersFlags); + + if (newLinkPin && !CanCreateLink(newLinkPin, &pin) && &pin != newLinkPin) + inputAlpha = (int)(255 * ImGui::GetStyle().Alpha * (48.0f / 255.0f)); + } + + //ImGui::Spring(1, 0); + ImGui::EndHorizontal(); + } + + ImGui::BeginHorizontal("content_frame"); + ImGui::Spring(1, padding); + + ImGui::BeginVertical("content", ImVec2(0.0f, 0.0f)); + ImGui::Dummy(ImVec2(160, 0)); + ImGui::Spring(1); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImGui::TextUnformatted(node.Name.c_str()); + ImGui::PopStyleColor(); + ImGui::Spring(1); + ImGui::EndVertical(); + auto contentRect = ImGui_GetItemRect(); + + ImGui::Spring(1, padding); + ImGui::EndHorizontal(); + + if (!node.Outputs.empty()) + { + ImGui::BeginHorizontal("outputs"); + ImGui::Spring(1, 0); + + ImRect outputsRect; + int outputAlpha = 200; + for (auto& pin : node.Outputs) + { + ImGui::Dummy(ImVec2(padding, padding)); + outputsRect = ImGui_GetItemRect(); + ImGui::Spring(1, 0); + outputsRect.Min.y += padding; + outputsRect.Max.y += padding; + +#if IMGUI_VERSION_NUM > 18101 + const auto allRoundCornersFlags = ImDrawFlags_RoundCornersAll; + const auto topRoundCornersFlags = ImDrawFlags_RoundCornersTop; +#else + const auto allRoundCornersFlags = 15; + const auto topRoundCornersFlags = 3; +#endif + + ed::PushStyleVar(ed::StyleVar_PinCorners, topRoundCornersFlags); + ed::BeginPin(pin.ID, ed::PinKind::Output); + ed::PinPivotRect(outputsRect.GetCenter(), outputsRect.GetCenter()); + ed::PinRect(outputsRect.GetTL(), outputsRect.GetBR()); + ed::EndPin(); + ed::PopStyleVar(); + + + auto drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(outputsRect.GetTL(), outputsRect.GetBR(), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), outputAlpha), 4.0f, allRoundCornersFlags); + drawList->AddRect(outputsRect.GetTL(), outputsRect.GetBR(), + IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), outputAlpha), 4.0f, allRoundCornersFlags); + + + if (newLinkPin && !CanCreateLink(newLinkPin, &pin) && &pin != newLinkPin) + outputAlpha = (int)(255 * ImGui::GetStyle().Alpha * (48.0f / 255.0f)); + } + + ImGui::EndHorizontal(); + } + + ImGui::EndVertical(); + + ed::EndNode(); + ed::PopStyleVar(7); + ed::PopStyleColor(4); + + // auto drawList = ed::GetNodeBackgroundDrawList(node.ID); + + //const auto fringeScale = ImGui::GetStyle().AntiAliasFringeScale; + //const auto unitSize = 1.0f / fringeScale; + + //const auto ImDrawList_AddRect = [](ImDrawList* drawList, const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners, float thickness) + //{ + // if ((col >> 24) == 0) + // return; + // drawList->PathRect(a, b, rounding, rounding_corners); + // drawList->PathStroke(col, true, thickness); + //}; + + //drawList->AddRectFilled(inputsRect.GetTL() + ImVec2(0, 1), inputsRect.GetBR(), + // IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), inputAlpha), 4.0f, 12); + //ImGui::PushStyleVar(ImGuiStyleVar_AntiAliasFringeScale, 1.0f); + //drawList->AddRect(inputsRect.GetTL() + ImVec2(0, 1), inputsRect.GetBR(), + // IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), inputAlpha), 4.0f, 12); + //ImGui::PopStyleVar(); + //drawList->AddRectFilled(outputsRect.GetTL(), outputsRect.GetBR() - ImVec2(0, 1), + // IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), outputAlpha), 4.0f, 3); + ////ImGui::PushStyleVar(ImGuiStyleVar_AntiAliasFringeScale, 1.0f); + //drawList->AddRect(outputsRect.GetTL(), outputsRect.GetBR() - ImVec2(0, 1), + // IM_COL32((int)(255 * pinBackground.x), (int)(255 * pinBackground.y), (int)(255 * pinBackground.z), outputAlpha), 4.0f, 3); + ////ImGui::PopStyleVar(); + //drawList->AddRectFilled(contentRect.GetTL(), contentRect.GetBR(), IM_COL32(24, 64, 128, 200), 0.0f); + //ImGui::PushStyleVar(ImGuiStyleVar_AntiAliasFringeScale, 1.0f); + //drawList->AddRect( + // contentRect.GetTL(), + // contentRect.GetBR(), + // IM_COL32(48, 128, 255, 100), 0.0f); + //ImGui::PopStyleVar(); + } + + for (auto& node : m_Nodes) + { + if (node.Type != NodeType::Comment) + continue; + + const float commentAlpha = 0.75f; + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, commentAlpha); + ed::PushStyleColor(ed::StyleColor_NodeBg, ImColor(255, 255, 255, 64)); + ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(255, 255, 255, 64)); + ed::BeginNode(node.ID); + ImGui::PushID(node.ID.AsPointer()); + ImGui::BeginVertical("content"); + ImGui::BeginHorizontal("horizontal"); + ImGui::Spring(1); + ImGui::TextUnformatted(node.Name.c_str()); + ImGui::Spring(1); + ImGui::EndHorizontal(); + ed::Group(node.Size); + ImGui::EndVertical(); + ImGui::PopID(); + ed::EndNode(); + ed::PopStyleColor(2); + ImGui::PopStyleVar(); + + if (ed::BeginGroupHint(node.ID)) + { + //auto alpha = static_cast(commentAlpha * ImGui::GetStyle().Alpha * 255); + auto bgAlpha = static_cast(ImGui::GetStyle().Alpha * 255); + + //ImGui::PushStyleVar(ImGuiStyleVar_Alpha, commentAlpha * ImGui::GetStyle().Alpha); + + auto min = ed::GetGroupMin(); + //auto max = ed::GetGroupMax(); + + ImGui::SetCursorScreenPos(min - ImVec2(-8, ImGui::GetTextLineHeightWithSpacing() + 4)); + ImGui::BeginGroup(); + ImGui::TextUnformatted(node.Name.c_str()); + ImGui::EndGroup(); + + auto drawList = ed::GetHintBackgroundDrawList(); + + auto hintBounds = ImGui_GetItemRect(); + auto hintFrameBounds = ImRect_Expanded(hintBounds, 8, 4); + + drawList->AddRectFilled( + hintFrameBounds.GetTL(), + hintFrameBounds.GetBR(), + IM_COL32(255, 255, 255, 64 * bgAlpha / 255), 4.0f); + + drawList->AddRect( + hintFrameBounds.GetTL(), + hintFrameBounds.GetBR(), + IM_COL32(255, 255, 255, 128 * bgAlpha / 255), 4.0f); + + //ImGui::PopStyleVar(); + } + ed::EndGroupHint(); + } + + for (auto& link : m_Links) + ed::Link(link.ID, link.StartPinID, link.EndPinID, link.Color, 2.0f); + + if (!createNewNode) + { + if (ed::BeginCreate(ImColor(255, 255, 255), 2.0f)) + { + auto showLabel = [](const char* label, ImColor color) + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetTextLineHeight()); + auto size = ImGui::CalcTextSize(label); + + auto padding = ImGui::GetStyle().FramePadding; + auto spacing = ImGui::GetStyle().ItemSpacing; + + ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(spacing.x, -spacing.y)); + + auto rectMin = ImGui::GetCursorScreenPos() - padding; + auto rectMax = ImGui::GetCursorScreenPos() + size + padding; + + auto drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(rectMin, rectMax, color, size.y * 0.15f); + ImGui::TextUnformatted(label); + }; + + ed::PinId startPinId = 0, endPinId = 0; + if (ed::QueryNewLink(&startPinId, &endPinId)) + { + auto startPin = FindPin(startPinId); + auto endPin = FindPin(endPinId); + + newLinkPin = startPin ? startPin : endPin; + + if (startPin->Kind == PinKind::Input) + { + std::swap(startPin, endPin); + std::swap(startPinId, endPinId); + } + + if (startPin && endPin) + { + if (endPin == startPin) + { + ed::RejectNewItem(ImColor(255, 0, 0), 2.0f); + } + else if (endPin->Kind == startPin->Kind) + { + showLabel("x Incompatible Pin Kind", ImColor(45, 32, 32, 180)); + ed::RejectNewItem(ImColor(255, 0, 0), 2.0f); + } + //else if (endPin->Node == startPin->Node) + //{ + // showLabel("x Cannot connect to self", ImColor(45, 32, 32, 180)); + // ed::RejectNewItem(ImColor(255, 0, 0), 1.0f); + //} + else if (endPin->Type != startPin->Type) + { + showLabel("x Incompatible Pin Type", ImColor(45, 32, 32, 180)); + ed::RejectNewItem(ImColor(255, 128, 128), 1.0f); + } + else + { + showLabel("+ Create Link", ImColor(32, 45, 32, 180)); + if (ed::AcceptNewItem(ImColor(128, 255, 128), 4.0f)) + { + m_Links.emplace_back(Link(GetNextId(), startPinId, endPinId)); + m_Links.back().Color = GetIconColor(startPin->Type); + } + } + } + } + + ed::PinId pinId = 0; + if (ed::QueryNewNode(&pinId)) + { + newLinkPin = FindPin(pinId); + if (newLinkPin) + showLabel("+ Create Node", ImColor(32, 45, 32, 180)); + + if (ed::AcceptNewItem()) + { + createNewNode = true; + newNodeLinkPin = FindPin(pinId); + newLinkPin = nullptr; + ed::Suspend(); + ImGui::OpenPopup("Create New Node"); + ed::Resume(); + } + } + } + else + newLinkPin = nullptr; + + ed::EndCreate(); + + if (ed::BeginDelete()) + { + ed::NodeId nodeId = 0; + while (ed::QueryDeletedNode(&nodeId)) + { + if (ed::AcceptDeletedItem()) + { + auto id = std::find_if(m_Nodes.begin(), m_Nodes.end(), [nodeId](auto& node) { return node.ID == nodeId; }); + if (id != m_Nodes.end()) + m_Nodes.erase(id); + } + } + + ed::LinkId linkId = 0; + while (ed::QueryDeletedLink(&linkId)) + { + if (ed::AcceptDeletedItem()) + { + auto id = std::find_if(m_Links.begin(), m_Links.end(), [linkId](auto& link) { return link.ID == linkId; }); + if (id != m_Links.end()) + m_Links.erase(id); + } + } + } + ed::EndDelete(); + } + + ImGui::SetCursorScreenPos(cursorTopLeft); + } + + # if 1 + auto openPopupPosition = ImGui::GetMousePos(); + ed::Suspend(); + if (ed::ShowNodeContextMenu(&contextNodeId)) + ImGui::OpenPopup("Node Context Menu"); + else if (ed::ShowPinContextMenu(&contextPinId)) + ImGui::OpenPopup("Pin Context Menu"); + else if (ed::ShowLinkContextMenu(&contextLinkId)) + ImGui::OpenPopup("Link Context Menu"); + else if (ed::ShowBackgroundContextMenu()) + { + ImGui::OpenPopup("Create New Node"); + newNodeLinkPin = nullptr; + } + ed::Resume(); + + ed::Suspend(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); + if (ImGui::BeginPopup("Node Context Menu")) + { + auto node = FindNode(contextNodeId); + + ImGui::TextUnformatted("Node Context Menu"); + ImGui::Separator(); + if (node) + { + ImGui::Text("ID: %p", node->ID.AsPointer()); + ImGui::Text("Type: %s", node->Type == NodeType::Blueprint ? "Blueprint" : (node->Type == NodeType::Tree ? "Tree" : "Comment")); + ImGui::Text("Inputs: %d", (int)node->Inputs.size()); + ImGui::Text("Outputs: %d", (int)node->Outputs.size()); + } + else + ImGui::Text("Unknown node: %p", contextNodeId.AsPointer()); + ImGui::Separator(); + if (ImGui::MenuItem("Delete")) + ed::DeleteNode(contextNodeId); + ImGui::EndPopup(); + } + + if (ImGui::BeginPopup("Pin Context Menu")) + { + auto pin = FindPin(contextPinId); + + ImGui::TextUnformatted("Pin Context Menu"); + ImGui::Separator(); + if (pin) + { + ImGui::Text("ID: %p", pin->ID.AsPointer()); + if (pin->Node) + ImGui::Text("Node: %p", pin->Node->ID.AsPointer()); + else + ImGui::Text("Node: %s", ""); + } + else + ImGui::Text("Unknown pin: %p", contextPinId.AsPointer()); + + ImGui::EndPopup(); + } + + if (ImGui::BeginPopup("Link Context Menu")) + { + auto link = FindLink(contextLinkId); + + ImGui::TextUnformatted("Link Context Menu"); + ImGui::Separator(); + if (link) + { + ImGui::Text("ID: %p", link->ID.AsPointer()); + ImGui::Text("From: %p", link->StartPinID.AsPointer()); + ImGui::Text("To: %p", link->EndPinID.AsPointer()); + } + else + ImGui::Text("Unknown link: %p", contextLinkId.AsPointer()); + ImGui::Separator(); + if (ImGui::MenuItem("Delete")) + ed::DeleteLink(contextLinkId); + ImGui::EndPopup(); + } + + if (ImGui::BeginPopup("Create New Node")) + { + auto newNodePostion = openPopupPosition; + //ImGui::SetCursorScreenPos(ImGui::GetMousePosOnOpeningCurrentPopup()); + + //auto drawList = ImGui::GetWindowDrawList(); + //drawList->AddCircleFilled(ImGui::GetMousePosOnOpeningCurrentPopup(), 10.0f, 0xFFFF00FF); + + Node* node = nullptr; + if (ImGui::MenuItem("Input Action")) + node = SpawnInputActionNode(); + if (ImGui::MenuItem("Output Action")) + node = SpawnOutputActionNode(); + if (ImGui::MenuItem("Branch")) + node = SpawnBranchNode(); + if (ImGui::MenuItem("Do N")) + node = SpawnDoNNode(); + if (ImGui::MenuItem("Set Timer")) + node = SpawnSetTimerNode(); + if (ImGui::MenuItem("Less")) + node = SpawnLessNode(); + if (ImGui::MenuItem("Weird")) + node = SpawnWeirdNode(); + if (ImGui::MenuItem("Trace by Channel")) + node = SpawnTraceByChannelNode(); + if (ImGui::MenuItem("Print String")) + node = SpawnPrintStringNode(); + ImGui::Separator(); + if (ImGui::MenuItem("Comment")) + node = SpawnComment(); + ImGui::Separator(); + if (ImGui::MenuItem("Sequence")) + node = SpawnTreeSequenceNode(); + if (ImGui::MenuItem("Move To")) + node = SpawnTreeTaskNode(); + if (ImGui::MenuItem("Random Wait")) + node = SpawnTreeTask2Node(); + ImGui::Separator(); + if (ImGui::MenuItem("Message")) + node = SpawnMessageNode(); + ImGui::Separator(); + if (ImGui::MenuItem("Transform")) + node = SpawnHoudiniTransformNode(); + if (ImGui::MenuItem("Group")) + node = SpawnHoudiniGroupNode(); + + if (node) + { + BuildNodes(); + + createNewNode = false; + + ed::SetNodePosition(node->ID, newNodePostion); + + if (auto startPin = newNodeLinkPin) + { + auto& pins = startPin->Kind == PinKind::Input ? node->Outputs : node->Inputs; + + for (auto& pin : pins) + { + if (CanCreateLink(startPin, &pin)) + { + auto endPin = &pin; + if (startPin->Kind == PinKind::Input) + std::swap(startPin, endPin); + + m_Links.emplace_back(Link(GetNextId(), startPin->ID, endPin->ID)); + m_Links.back().Color = GetIconColor(startPin->Type); + + break; + } + } + } + } + + ImGui::EndPopup(); + } + else + createNewNode = false; + ImGui::PopStyleVar(); + ed::Resume(); + # endif + + + /* + cubic_bezier_t c; + c.p0 = pointf(100, 600); + c.p1 = pointf(300, 1200); + c.p2 = pointf(500, 100); + c.p3 = pointf(900, 600); + + auto drawList = ImGui::GetWindowDrawList(); + auto offset_radius = 15.0f; + auto acceptPoint = [drawList, offset_radius](const bezier_subdivide_result_t& r) + { + drawList->AddCircle(to_imvec(r.point), 4.0f, IM_COL32(255, 0, 255, 255)); + + auto nt = r.tangent.normalized(); + nt = pointf(-nt.y, nt.x); + + drawList->AddLine(to_imvec(r.point), to_imvec(r.point + nt * offset_radius), IM_COL32(255, 0, 0, 255), 1.0f); + }; + + drawList->AddBezierCurve(to_imvec(c.p0), to_imvec(c.p1), to_imvec(c.p2), to_imvec(c.p3), IM_COL32(255, 255, 255, 255), 1.0f); + cubic_bezier_subdivide(acceptPoint, c); + */ + + ed::End(); + + auto editorMin = ImGui::GetItemRectMin(); + auto editorMax = ImGui::GetItemRectMax(); + + if (m_ShowOrdinals) + { + int nodeCount = ed::GetNodeCount(); + std::vector orderedNodeIds; + orderedNodeIds.resize(static_cast(nodeCount)); + ed::GetOrderedNodeIds(orderedNodeIds.data(), nodeCount); + + + auto drawList = ImGui::GetWindowDrawList(); + drawList->PushClipRect(editorMin, editorMax); + + int ordinal = 0; + for (auto& nodeId : orderedNodeIds) + { + auto p0 = ed::GetNodePosition(nodeId); + auto p1 = p0 + ed::GetNodeSize(nodeId); + p0 = ed::CanvasToScreen(p0); + p1 = ed::CanvasToScreen(p1); + + + ImGuiTextBuffer builder; + builder.appendf("#%d", ordinal++); + + auto textSize = ImGui::CalcTextSize(builder.c_str()); + auto padding = ImVec2(2.0f, 2.0f); + auto widgetSize = textSize + padding * 2; + + auto widgetPosition = ImVec2(p1.x, p0.y) + ImVec2(0.0f, -widgetSize.y); + + drawList->AddRectFilled(widgetPosition, widgetPosition + widgetSize, IM_COL32(100, 80, 80, 190), 3.0f, ImDrawFlags_RoundCornersAll); + drawList->AddRect(widgetPosition, widgetPosition + widgetSize, IM_COL32(200, 160, 160, 190), 3.0f, ImDrawFlags_RoundCornersAll); + drawList->AddText(widgetPosition + padding, IM_COL32(255, 255, 255, 255), builder.c_str()); + } + + drawList->PopClipRect(); + } + + + //ImGui::ShowTestWindow(); + //ImGui::ShowMetricsWindow(); + } + + int m_NextId = 1; + const int m_PinIconSize = 24; + std::vector m_Nodes; + std::vector m_Links; + ImTextureID m_HeaderBackground = nullptr; + ImTextureID m_SaveIcon = nullptr; + ImTextureID m_RestoreIcon = nullptr; + const float m_TouchTime = 1.0f; + std::map m_NodeTouchTime; + bool m_ShowOrdinals = false; +}; + +int Main(int argc, char** argv) +{ + Example exampe("Blueprints", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/data/BlueprintBackground.png b/cpp/vendor/imgui-node-editor/examples/blueprints-example/data/BlueprintBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..ce7edce3ad7f46987a20a0a814dd1a55b11202e8 GIT binary patch literal 5513 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 eaA@0d?z>-rMJX$#4*z=q0000U8 literal 0 HcmV?d00001 diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/data/ic_save_white_24dp.png b/cpp/vendor/imgui-node-editor/examples/blueprints-example/data/ic_save_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..015062ed3b316909362e3bfe43579715a4f2c14e GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+id`}n05R2ZcmmRqp6hvGD%@cNQ zTrf?MD{tY`DCrY!+h+!7)MX@<|M>91UCT6U&6_Q^vKIW^bNn|gNPwG~q2dAY{6@(2Ul)|NDzIgd>D5-Nv3R_&pT)_9!>hC0v RLqMw;JYD@<);T3K0RZw0JMaJi literal 0 HcmV?d00001 diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.cpp b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.cpp new file mode 100644 index 00000000..e8ae020a --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.cpp @@ -0,0 +1,310 @@ +//------------------------------------------------------------------------------ +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# define IMGUI_DEFINE_MATH_OPERATORS +# include "builders.h" +# include + + +//------------------------------------------------------------------------------ +namespace ed = ax::NodeEditor; +namespace util = ax::NodeEditor::Utilities; + +util::BlueprintNodeBuilder::BlueprintNodeBuilder(ImTextureID texture, int textureWidth, int textureHeight): + HeaderTextureId(texture), + HeaderTextureWidth(textureWidth), + HeaderTextureHeight(textureHeight), + CurrentNodeId(0), + CurrentStage(Stage::Invalid), + HasHeader(false) +{ +} + +void util::BlueprintNodeBuilder::Begin(ed::NodeId id) +{ + HasHeader = false; + HeaderMin = HeaderMax = ImVec2(); + + ed::PushStyleVar(StyleVar_NodePadding, ImVec4(8, 4, 8, 8)); + + ed::BeginNode(id); + + ImGui::PushID(id.AsPointer()); + CurrentNodeId = id; + + SetStage(Stage::Begin); +} + +void util::BlueprintNodeBuilder::End() +{ + SetStage(Stage::End); + + ed::EndNode(); + + if (ImGui::IsItemVisible()) + { + auto alpha = static_cast(255 * ImGui::GetStyle().Alpha); + + auto drawList = ed::GetNodeBackgroundDrawList(CurrentNodeId); + + const auto halfBorderWidth = ed::GetStyle().NodeBorderWidth * 0.5f; + + auto headerColor = IM_COL32(0, 0, 0, alpha) | (HeaderColor & IM_COL32(255, 255, 255, 0)); + if ((HeaderMax.x > HeaderMin.x) && (HeaderMax.y > HeaderMin.y) && HeaderTextureId) + { + const auto uv = ImVec2( + (HeaderMax.x - HeaderMin.x) / (float)(4.0f * HeaderTextureWidth), + (HeaderMax.y - HeaderMin.y) / (float)(4.0f * HeaderTextureHeight)); + + drawList->AddImageRounded(HeaderTextureId, + HeaderMin - ImVec2(8 - halfBorderWidth, 4 - halfBorderWidth), + HeaderMax + ImVec2(8 - halfBorderWidth, 0), + ImVec2(0.0f, 0.0f), uv, +#if IMGUI_VERSION_NUM > 18101 + headerColor, GetStyle().NodeRounding, ImDrawFlags_RoundCornersTop); +#else + headerColor, GetStyle().NodeRounding, 1 | 2); +#endif + + if (ContentMin.y > HeaderMax.y) + { + drawList->AddLine( + ImVec2(HeaderMin.x - (8 - halfBorderWidth), HeaderMax.y - 0.5f), + ImVec2(HeaderMax.x + (8 - halfBorderWidth), HeaderMax.y - 0.5f), + ImColor(255, 255, 255, 96 * alpha / (3 * 255)), 1.0f); + } + } + } + + CurrentNodeId = 0; + + ImGui::PopID(); + + ed::PopStyleVar(); + + SetStage(Stage::Invalid); +} + +void util::BlueprintNodeBuilder::Header(const ImVec4& color) +{ + HeaderColor = ImColor(color); + SetStage(Stage::Header); +} + +void util::BlueprintNodeBuilder::EndHeader() +{ + SetStage(Stage::Content); +} + +void util::BlueprintNodeBuilder::Input(ed::PinId id) +{ + if (CurrentStage == Stage::Begin) + SetStage(Stage::Content); + + const auto applyPadding = (CurrentStage == Stage::Input); + + SetStage(Stage::Input); + + if (applyPadding) + ImGui::Spring(0); + + Pin(id, PinKind::Input); + + ImGui::BeginHorizontal(id.AsPointer()); +} + +void util::BlueprintNodeBuilder::EndInput() +{ + ImGui::EndHorizontal(); + + EndPin(); +} + +void util::BlueprintNodeBuilder::Middle() +{ + if (CurrentStage == Stage::Begin) + SetStage(Stage::Content); + + SetStage(Stage::Middle); +} + +void util::BlueprintNodeBuilder::Output(ed::PinId id) +{ + if (CurrentStage == Stage::Begin) + SetStage(Stage::Content); + + const auto applyPadding = (CurrentStage == Stage::Output); + + SetStage(Stage::Output); + + if (applyPadding) + ImGui::Spring(0); + + Pin(id, PinKind::Output); + + ImGui::BeginHorizontal(id.AsPointer()); +} + +void util::BlueprintNodeBuilder::EndOutput() +{ + ImGui::EndHorizontal(); + + EndPin(); +} + +bool util::BlueprintNodeBuilder::SetStage(Stage stage) +{ + if (stage == CurrentStage) + return false; + + auto oldStage = CurrentStage; + CurrentStage = stage; + + ImVec2 cursor; + switch (oldStage) + { + case Stage::Begin: + break; + + case Stage::Header: + ImGui::EndHorizontal(); + HeaderMin = ImGui::GetItemRectMin(); + HeaderMax = ImGui::GetItemRectMax(); + + // spacing between header and content + ImGui::Spring(0, ImGui::GetStyle().ItemSpacing.y * 2.0f); + + break; + + case Stage::Content: + break; + + case Stage::Input: + ed::PopStyleVar(2); + + ImGui::Spring(1, 0); + ImGui::EndVertical(); + + // #debug + // ImGui::GetWindowDrawList()->AddRect( + // ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 255)); + + break; + + case Stage::Middle: + ImGui::EndVertical(); + + // #debug + // ImGui::GetWindowDrawList()->AddRect( + // ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 255)); + + break; + + case Stage::Output: + ed::PopStyleVar(2); + + ImGui::Spring(1, 0); + ImGui::EndVertical(); + + // #debug + // ImGui::GetWindowDrawList()->AddRect( + // ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 255)); + + break; + + case Stage::End: + break; + + case Stage::Invalid: + break; + } + + switch (stage) + { + case Stage::Begin: + ImGui::BeginVertical("node"); + break; + + case Stage::Header: + HasHeader = true; + + ImGui::BeginHorizontal("header"); + break; + + case Stage::Content: + if (oldStage == Stage::Begin) + ImGui::Spring(0); + + ImGui::BeginHorizontal("content"); + ImGui::Spring(0, 0); + break; + + case Stage::Input: + ImGui::BeginVertical("inputs", ImVec2(0, 0), 0.0f); + + ed::PushStyleVar(ed::StyleVar_PivotAlignment, ImVec2(0, 0.5f)); + ed::PushStyleVar(ed::StyleVar_PivotSize, ImVec2(0, 0)); + + if (!HasHeader) + ImGui::Spring(1, 0); + break; + + case Stage::Middle: + ImGui::Spring(1); + ImGui::BeginVertical("middle", ImVec2(0, 0), 1.0f); + break; + + case Stage::Output: + if (oldStage == Stage::Middle || oldStage == Stage::Input) + ImGui::Spring(1); + else + ImGui::Spring(1, 0); + ImGui::BeginVertical("outputs", ImVec2(0, 0), 1.0f); + + ed::PushStyleVar(ed::StyleVar_PivotAlignment, ImVec2(1.0f, 0.5f)); + ed::PushStyleVar(ed::StyleVar_PivotSize, ImVec2(0, 0)); + + if (!HasHeader) + ImGui::Spring(1, 0); + break; + + case Stage::End: + if (oldStage == Stage::Input) + ImGui::Spring(1, 0); + if (oldStage != Stage::Begin) + ImGui::EndHorizontal(); + ContentMin = ImGui::GetItemRectMin(); + ContentMax = ImGui::GetItemRectMax(); + + //ImGui::Spring(0); + ImGui::EndVertical(); + NodeMin = ImGui::GetItemRectMin(); + NodeMax = ImGui::GetItemRectMax(); + break; + + case Stage::Invalid: + break; + } + + return true; +} + +void util::BlueprintNodeBuilder::Pin(ed::PinId id, ed::PinKind kind) +{ + ed::BeginPin(id, kind); +} + +void util::BlueprintNodeBuilder::EndPin() +{ + ed::EndPin(); + + // #debug + // ImGui::GetWindowDrawList()->AddRectFilled( + // ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 64)); +} diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.h b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.h new file mode 100644 index 00000000..ef5db2c1 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/builders.h @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# pragma once + + +//------------------------------------------------------------------------------ +# include + + +//------------------------------------------------------------------------------ +namespace ax { +namespace NodeEditor { +namespace Utilities { + + +//------------------------------------------------------------------------------ +struct BlueprintNodeBuilder +{ + BlueprintNodeBuilder(ImTextureID texture = nullptr, int textureWidth = 0, int textureHeight = 0); + + void Begin(NodeId id); + void End(); + + void Header(const ImVec4& color = ImVec4(1, 1, 1, 1)); + void EndHeader(); + + void Input(PinId id); + void EndInput(); + + void Middle(); + + void Output(PinId id); + void EndOutput(); + + +private: + enum class Stage + { + Invalid, + Begin, + Header, + Content, + Input, + Output, + Middle, + End + }; + + bool SetStage(Stage stage); + + void Pin(PinId id, ax::NodeEditor::PinKind kind); + void EndPin(); + + ImTextureID HeaderTextureId; + int HeaderTextureWidth; + int HeaderTextureHeight; + NodeId CurrentNodeId; + Stage CurrentStage; + ImU32 HeaderColor; + ImVec2 NodeMin; + ImVec2 NodeMax; + ImVec2 HeaderMin; + ImVec2 HeaderMax; + ImVec2 ContentMin; + ImVec2 ContentMax; + bool HasHeader; +}; + + + +//------------------------------------------------------------------------------ +} // namespace Utilities +} // namespace Editor +} // namespace ax \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.cpp b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.cpp new file mode 100644 index 00000000..1f255eec --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.cpp @@ -0,0 +1,252 @@ +# define IMGUI_DEFINE_MATH_OPERATORS +# include "drawing.h" +# include + +void ax::Drawing::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) +{ + auto rect = ImRect(a, b); + auto rect_x = rect.Min.x; + auto rect_y = rect.Min.y; + auto rect_w = rect.Max.x - rect.Min.x; + auto rect_h = rect.Max.y - rect.Min.y; + auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; + auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; + auto rect_center = ImVec2(rect_center_x, rect_center_y); + const auto outline_scale = rect_w / 24.0f; + const auto extra_segments = static_cast(2 * outline_scale); // for full circle + + if (type == IconType::Flow) + { + const auto origin_scale = rect_w / 24.0f; + + const auto offset_x = 1.0f * origin_scale; + const auto offset_y = 0.0f * origin_scale; + const auto margin = (filled ? 2.0f : 2.0f) * origin_scale; + const auto rounding = 0.1f * origin_scale; + const auto tip_round = 0.7f; // percentage of triangle edge (for tip) + //const auto edge_round = 0.7f; // percentage of triangle edge (for corner) + const auto canvas = ImRect( + rect.Min.x + margin + offset_x, + rect.Min.y + margin + offset_y, + rect.Max.x - margin + offset_x, + rect.Max.y - margin + offset_y); + const auto canvas_x = canvas.Min.x; + const auto canvas_y = canvas.Min.y; + const auto canvas_w = canvas.Max.x - canvas.Min.x; + const auto canvas_h = canvas.Max.y - canvas.Min.y; + + const auto left = canvas_x + canvas_w * 0.5f * 0.3f; + const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; + const auto top = canvas_y + canvas_h * 0.5f * 0.2f; + const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; + const auto center_y = (top + bottom) * 0.5f; + //const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; + + const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); + const auto tip_right = ImVec2(right, center_y); + const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); + + drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); + drawList->PathBezierCubicCurveTo( + ImVec2(left, top), + ImVec2(left, top), + ImVec2(left, top) + ImVec2(rounding, 0)); + drawList->PathLineTo(tip_top); + drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); + drawList->PathBezierCubicCurveTo( + tip_right, + tip_right, + tip_bottom + (tip_right - tip_bottom) * tip_round); + drawList->PathLineTo(tip_bottom); + drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); + drawList->PathBezierCubicCurveTo( + ImVec2(left, bottom), + ImVec2(left, bottom), + ImVec2(left, bottom) - ImVec2(0, rounding)); + + if (!filled) + { + if (innerColor & 0xFF000000) + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } + else + drawList->PathFillConvex(color); + } + else + { + auto triangleStart = rect_center_x + 0.32f * rect_w; + + auto rect_offset = -static_cast(rect_w * 0.25f * 0.25f); + + rect.Min.x += rect_offset; + rect.Max.x += rect_offset; + rect_x += rect_offset; + rect_center_x += rect_offset * 0.5f; + rect_center.x += rect_offset * 0.5f; + + if (type == IconType::Circle) + { + const auto c = rect_center; + + if (!filled) + { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + + if (innerColor & 0xFF000000) + drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); + drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); + } + else + { + drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); + } + } + + if (type == IconType::Square) + { + if (filled) + { + const auto r = 0.5f * rect_w / 2.0f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + +#if IMGUI_VERSION_NUM > 18101 + drawList->AddRectFilled(p0, p1, color, 0, ImDrawFlags_RoundCornersAll); +#else + drawList->AddRectFilled(p0, p1, color, 0, 15); +#endif + } + else + { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + { +#if IMGUI_VERSION_NUM > 18101 + drawList->AddRectFilled(p0, p1, innerColor, 0, ImDrawFlags_RoundCornersAll); +#else + drawList->AddRectFilled(p0, p1, innerColor, 0, 15); +#endif + } + +#if IMGUI_VERSION_NUM > 18101 + drawList->AddRect(p0, p1, color, 0, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale); +#else + drawList->AddRect(p0, p1, color, 0, 15, 2.0f * outline_scale); +#endif + } + } + + if (type == IconType::Grid) + { + const auto r = 0.5f * rect_w / 2.0f; + const auto w = ceilf(r / 3.0f); + + const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); + const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); + + auto tl = baseTl; + auto br = baseBr; + for (int i = 0; i < 3; ++i) + { + tl.x = baseTl.x; + br.x = baseBr.x; + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + if (i != 1 || filled) + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + drawList->AddRectFilled(tl, br, color); + + tl.y += w * 2; + br.y += w * 2; + } + + triangleStart = br.x + w + 1.0f / 24.0f * rect_w; + } + + if (type == IconType::RoundSquare) + { + if (filled) + { + const auto r = 0.5f * rect_w / 2.0f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + +#if IMGUI_VERSION_NUM > 18101 + drawList->AddRectFilled(p0, p1, color, cr, ImDrawFlags_RoundCornersAll); +#else + drawList->AddRectFilled(p0, p1, color, cr, 15); +#endif + } + else + { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + { +#if IMGUI_VERSION_NUM > 18101 + drawList->AddRectFilled(p0, p1, innerColor, cr, ImDrawFlags_RoundCornersAll); +#else + drawList->AddRectFilled(p0, p1, innerColor, cr, 15); +#endif + } + +#if IMGUI_VERSION_NUM > 18101 + drawList->AddRect(p0, p1, color, cr, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale); +#else + drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); +#endif + } + } + else if (type == IconType::Diamond) + { + if (filled) + { + const auto r = 0.607f * rect_w / 2.0f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2( 0, -r)); + drawList->PathLineTo(c + ImVec2( r, 0)); + drawList->PathLineTo(c + ImVec2( 0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + drawList->PathFillConvex(color); + } + else + { + const auto r = 0.607f * rect_w / 2.0f - 0.5f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2( 0, -r)); + drawList->PathLineTo(c + ImVec2( r, 0)); + drawList->PathLineTo(c + ImVec2( 0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + + if (innerColor & 0xFF000000) + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } + } + else + { + const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); + + drawList->AddTriangleFilled( + ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), + ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), + ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), + color); + } + } +} diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.h b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.h new file mode 100644 index 00000000..4387c58c --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/drawing.h @@ -0,0 +1,12 @@ +# pragma once +# include + +namespace ax { +namespace Drawing { + +enum class IconType: ImU32 { Flow, Circle, Square, Grid, RoundSquare, Diamond }; + +void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor); + +} // namespace Drawing +} // namespace ax \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.cpp b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.cpp new file mode 100644 index 00000000..202c8e23 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.cpp @@ -0,0 +1,16 @@ +# define IMGUI_DEFINE_MATH_OPERATORS +# include "widgets.h" +# include + +void ax::Widgets::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, const ImVec4& innerColor/* = ImVec4(0, 0, 0, 0)*/) +{ + if (ImGui::IsRectVisible(size)) + { + auto cursorPos = ImGui::GetCursorScreenPos(); + auto drawList = ImGui::GetWindowDrawList(); + ax::Drawing::DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); + } + + ImGui::Dummy(size); +} + diff --git a/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.h b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.h new file mode 100644 index 00000000..09b39462 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/blueprints-example/utilities/widgets.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include "drawing.h" + +namespace ax { +namespace Widgets { + +using Drawing::IconType; + +void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0)); + +} // namespace Widgets +} // namespace ax \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/canvas-example/CMakeLists.txt b/cpp/vendor/imgui-node-editor/examples/canvas-example/CMakeLists.txt new file mode 100644 index 00000000..11f1c89a --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/canvas-example/CMakeLists.txt @@ -0,0 +1,5 @@ +add_example_executable(canvas-example + canvas-example.cpp +) + +#target_link_libraries(Canvas PRIVATE imgui_canvas) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/canvas-example/canvas-example.cpp b/cpp/vendor/imgui-node-editor/examples/canvas-example/canvas-example.cpp new file mode 100644 index 00000000..f07c3e38 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/canvas-example/canvas-example.cpp @@ -0,0 +1,251 @@ +# define IMGUI_DEFINE_MATH_OPERATORS +# include +# include +# include +# include + +static void DrawScale(const ImVec2& from, const ImVec2& to, float majorUnit, float minorUnit, float labelAlignment, float sign = 1.0f) +{ + auto drawList = ImGui::GetWindowDrawList(); + auto direction = (to - from) * ImInvLength(to - from, 0.0f); + auto normal = ImVec2(-direction.y, direction.x); + auto distance = sqrtf(ImLengthSqr(to - from)); + + if (ImDot(direction, direction) < FLT_EPSILON) + return; + + auto minorSize = 5.0f; + auto majorSize = 10.0f; + auto labelDistance = 8.0f; + + drawList->AddLine(from, to, IM_COL32(255, 255, 255, 255)); + + auto p = from; + for (auto d = 0.0f; d <= distance; d += minorUnit, p += direction * minorUnit) + drawList->AddLine(p - normal * minorSize, p + normal * minorSize, IM_COL32(255, 255, 255, 255)); + + for (auto d = 0.0f; d <= distance + majorUnit; d += majorUnit) + { + p = from + direction * d; + + drawList->AddLine(p - normal * majorSize, p + normal * majorSize, IM_COL32(255, 255, 255, 255)); + + if (d == 0.0f) + continue; + + char label[16]; + snprintf(label, 15, "%g", d * sign); + auto labelSize = ImGui::CalcTextSize(label); + + auto labelPosition = p + ImVec2(fabsf(normal.x), fabsf(normal.y)) * labelDistance; + auto labelAlignedSize = ImDot(labelSize, direction); + labelPosition += direction * (-labelAlignedSize + labelAlignment * labelAlignedSize * 2.0f); + labelPosition = ImFloor(labelPosition + ImVec2(0.5f, 0.5f)); + + drawList->AddText(labelPosition, IM_COL32(255, 255, 255, 255), label); + } +} + +static bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f) +{ + using namespace ImGui; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); + return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f); +} + +struct Example: + public Application +{ + using Application::Application; + + void OnFrame(float deltaTime) override + { + auto& io = ImGui::GetIO(); + + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + + ImGui::Separator(); + + auto availableRegion = ImGui::GetContentRegionAvail(); + + static float s_SplitterSize = 6.0f; + static float s_SplitterArea = 0.0f; + static float s_LeftPaneSize = 0.0f; + static float s_RightPaneSize = 0.0f; + + if (s_SplitterArea != availableRegion.x) + { + if (s_SplitterArea == 0.0f) + { + s_SplitterArea = availableRegion.x; + s_LeftPaneSize = ImFloor(availableRegion.x * 0.25f); + s_RightPaneSize = availableRegion.x - s_LeftPaneSize - s_SplitterSize; + } + else + { + auto ratio = availableRegion.x / s_SplitterArea; + s_SplitterArea = availableRegion.x; + s_LeftPaneSize = s_LeftPaneSize * ratio; + s_RightPaneSize = availableRegion.x - s_LeftPaneSize - s_SplitterSize; + } + } + + static ImGuiEx::Canvas canvas; + static ImVec2 drawStartPoint; + static bool isDragging = false; + static ImRect panelRect; + + Splitter(true, s_SplitterSize, &s_LeftPaneSize, &s_RightPaneSize, 100.0f, 100.0f); + + auto canvasRect = canvas.Rect(); + auto viewRect = canvas.ViewRect(); + auto viewOrigin = canvas.ViewOrigin(); + auto viewScale = canvas.ViewScale(); + + ImGui::BeginChild("##top", ImVec2(s_LeftPaneSize, -1), false, ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::TextUnformatted("Rect:"); + ImGui::BeginColumns("rect", 2, ImGuiOldColumnFlags_NoBorder); + ImGui::SetColumnWidth(0, ImGui::CalcTextSize("\t\tL: 0000.00\t").x); + ImGui::Text("\tL: %.2f", canvasRect.Min.x); ImGui::NextColumn(); + ImGui::Text("\tT: %.2f", canvasRect.Min.y); ImGui::NextColumn(); + ImGui::Text("\tR: %.2f", canvasRect.Max.x); ImGui::NextColumn(); + ImGui::Text("\tB: %.2f", canvasRect.Max.y); ImGui::NextColumn(); + ImGui::Text("\tW: %.2f", canvasRect.GetWidth()); ImGui::NextColumn(); + ImGui::Text("\tH: %.2f", canvasRect.GetHeight()); ImGui::NextColumn(); + ImGui::EndColumns(); + + ImGui::TextUnformatted("View Rect:"); + ImGui::BeginColumns("viewrect", 2, ImGuiOldColumnFlags_NoBorder); + ImGui::SetColumnWidth(0, ImGui::CalcTextSize("\t\tL: 0000.00\t").x); + ImGui::Text("\tL: %.2f", viewRect.Min.x); ImGui::NextColumn(); + ImGui::Text("\tT: %.2f", viewRect.Min.y); ImGui::NextColumn(); + ImGui::Text("\tR: %.2f", viewRect.Max.x); ImGui::NextColumn(); + ImGui::Text("\tB: %.2f", viewRect.Max.y); ImGui::NextColumn(); + ImGui::Text("\tW: %.2f", viewRect.GetWidth()); ImGui::NextColumn(); + ImGui::Text("\tH: %.2f", viewRect.GetHeight()); ImGui::NextColumn(); + ImGui::EndColumns(); + + ImGui::TextUnformatted("Origin:"); + ImGui::Indent(); + auto originChanged = false; + ImGui::PushItemWidth(-ImGui::GetStyle().IndentSpacing); + originChanged |= ImGui::DragFloat("##originx", &viewOrigin.x, 1.0f); + originChanged |= ImGui::DragFloat("##originy", &viewOrigin.y, 1.0f); + if (originChanged) canvas.SetView(viewOrigin, viewScale); + ImGui::PopItemWidth(); + ImGui::Unindent(); + + ImGui::TextUnformatted("Scale:"); + ImGui::Indent(); + ImGui::PushItemWidth(-ImGui::GetStyle().IndentSpacing); + if (ImGui::DragFloat("##scale", &viewScale, 0.01f, 0.01f, 15.0f)) + canvas.SetView(viewOrigin, viewScale); + ImGui::PopItemWidth(); + ImGui::Unindent(); + + ImGui::Separator(); + + if (ImGui::Button("Center over Panel", ImVec2(s_LeftPaneSize, 0))) + canvas.CenterView(panelRect.GetCenter()); + + if (ImGui::Button("Center and zoom to Panel", ImVec2(s_LeftPaneSize, 0))) + canvas.CenterView(panelRect); + + ImGui::TextUnformatted("Panel Rect:"); + ImGui::BeginColumns("panelrect", 2, ImGuiOldColumnFlags_NoBorder); + ImGui::SetColumnWidth(0, ImGui::CalcTextSize("\t\tL: 0000.00\t").x); + ImGui::Text("\tL: %.2f", panelRect.Min.x); ImGui::NextColumn(); + ImGui::Text("\tT: %.2f", panelRect.Min.y); ImGui::NextColumn(); + ImGui::Text("\tR: %.2f", panelRect.Max.x); ImGui::NextColumn(); + ImGui::Text("\tB: %.2f", panelRect.Max.y); ImGui::NextColumn(); + ImGui::Text("\tW: %.2f", panelRect.GetWidth()); ImGui::NextColumn(); + ImGui::Text("\tH: %.2f", panelRect.GetHeight()); ImGui::NextColumn(); + ImGui::EndColumns(); + + ImGui::EndChild(); + + ImGui::SameLine(0.0f, s_SplitterSize); + + + if (canvas.Begin("##mycanvas", ImVec2(s_RightPaneSize, 0.0f))) + { + //auto drawList = ImGui::GetWindowDrawList(); + + if ((isDragging || ImGui::IsItemHovered()) && ImGui::IsMouseDragging(1, 0.0f)) + { + if (!isDragging) + { + isDragging = true; + drawStartPoint = viewOrigin; + } + + canvas.SetView(drawStartPoint + ImGui::GetMouseDragDelta(1, 0.0f) * viewScale, viewScale); + } + else if (isDragging) + isDragging = false; + + viewRect = canvas.ViewRect(); + + if (viewRect.Max.x > 0.0f) + DrawScale(ImVec2(0.0f, 0.0f), ImVec2(viewRect.Max.x, 0.0f), 100.0f, 10.0f, 0.6f); + if (viewRect.Min.x < 0.0f) + DrawScale(ImVec2(0.0f, 0.0f), ImVec2(viewRect.Min.x, 0.0f), 100.0f, 10.0f, 0.6f, -1.0f); + if (viewRect.Max.y > 0.0f) + DrawScale(ImVec2(0.0f, 0.0f), ImVec2(0.0f, viewRect.Max.y), 100.0f, 10.0f, 0.6f); + if (viewRect.Min.y < 0.0f) + DrawScale(ImVec2(0.0f, 0.0f), ImVec2(0.0f, viewRect.Min.y), 100.0f, 10.0f, 0.6f, -1.0f); + + ImGui::Text("Hovered: %d", ImGui::IsItemHovered() ? 1 : 0); + + ImGui::TextUnformatted("Hello World!"); + + ImGui::Bullet(); + + ImGui::Button("Panel", ImVec2(s_RightPaneSize * 0.75f, availableRegion.y * 0.5f) * 0.5f); + panelRect.Min = ImGui::GetItemRectMin(); + panelRect.Max = ImGui::GetItemRectMax(); + + canvas.End(); + } + + + + + + + //ed::SetCurrentEditor(g_Context); + //ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + //int uniqueId = 1; + //// Start drawing nodes. + //ed::BeginNode(uniqueId++); + // ImGui::Text("Node A"); + // ed::BeginPin(uniqueId++, ed::PinKind::Input); + // ImGui::Text("-> In"); + // ed::EndPin(); + // ImGui::SameLine(); + // ed::BeginPin(uniqueId++, ed::PinKind::Output); + // ImGui::Text("Out ->"); + // ed::EndPin(); + //ed::EndNode(); + //ed::End(); + //ed::SetCurrentEditor(nullptr); + + //ImGui::ShowMetricsWindow(); + } +}; + +int Main(int argc, char** argv) +{ + Example exampe("Canvas", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/data/Cuprum-Bold.ttf b/cpp/vendor/imgui-node-editor/examples/data/Cuprum-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d56cd44e3c2486cf96d65c0d17e4fe6123567208 GIT binary patch literal 96364 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 r_L}Eb^V}(OyrDK|=|! literal 0 HcmV?d00001 diff --git a/cpp/vendor/imgui-node-editor/examples/data/Cuprum-OFL.txt b/cpp/vendor/imgui-node-editor/examples/data/Cuprum-OFL.txt new file mode 100644 index 00000000..6acddd01 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/data/Cuprum-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Cuprum Project Authors (lemonad@jovanny.ru), with Reserved Font Name "Cuprum". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/cpp/vendor/imgui-node-editor/examples/data/Oswald-OFL.txt b/cpp/vendor/imgui-node-editor/examples/data/Oswald-OFL.txt new file mode 100644 index 00000000..7e2c1520 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/data/Oswald-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/cpp/vendor/imgui-node-editor/examples/data/Oswald-Regular.ttf b/cpp/vendor/imgui-node-editor/examples/data/Oswald-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2492c44a2de20b40ee64b10d8da1f8129227c78f GIT binary patch literal 91400 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 literal 0 HcmV?d00001 diff --git a/cpp/vendor/imgui-node-editor/examples/data/Play-OFL.txt b/cpp/vendor/imgui-node-editor/examples/data/Play-OFL.txt new file mode 100644 index 00000000..cb9baa96 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/data/Play-OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Jonas Hecksher, Playtypes, e-types AS (lasse@e-types.com), with Reserved Font Name 'Play', 'Playtype', 'Playtype Sans'. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/cpp/vendor/imgui-node-editor/examples/data/Play-Regular.ttf b/cpp/vendor/imgui-node-editor/examples/data/Play-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25a72a746cf4061c7a19b22aa9d0039e4a801ac7 GIT binary patch literal 183852 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
i`FVMT8^_<$mwxpJ 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 Bic +# include +# include + +namespace ed = ax::NodeEditor; + +struct Example: + public Application +{ + using Application::Application; + + void OnStart() override + { + ed::Config config; + config.SettingsFile = "Simple.json"; + m_Context = ed::CreateEditor(&config); + } + + void OnStop() override + { + ed::DestroyEditor(m_Context); + } + + void OnFrame(float deltaTime) override + { + auto& io = ImGui::GetIO(); + + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + + ImGui::Separator(); + + ed::SetCurrentEditor(m_Context); + ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + int uniqueId = 1; + // Start drawing nodes. + ed::BeginNode(uniqueId++); + ImGui::Text("Node A"); + ed::BeginPin(uniqueId++, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ed::BeginPin(uniqueId++, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + ed::EndNode(); + ed::End(); + ed::SetCurrentEditor(nullptr); + + //ImGui::ShowMetricsWindow(); + } + + ed::EditorContext* m_Context = nullptr; +}; + +int Main(int argc, char** argv) +{ + Example exampe("Simple", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/widgets-example/CMakeLists.txt b/cpp/vendor/imgui-node-editor/examples/widgets-example/CMakeLists.txt new file mode 100644 index 00000000..1f1080a4 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/widgets-example/CMakeLists.txt @@ -0,0 +1,3 @@ +add_example_executable(widgets-example + widgets-example.cpp +) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/examples/widgets-example/widgets-example.cpp b/cpp/vendor/imgui-node-editor/examples/widgets-example/widgets-example.cpp new file mode 100644 index 00000000..64314261 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/examples/widgets-example/widgets-example.cpp @@ -0,0 +1,432 @@ +// =================================================================================================================== +// Widget Example +// Drawing standard ImGui widgets inside the node body +// +// First, some unsorted notes about which widgets do and don't draw well inside nodes. Run the examples to see all the allowed widgets. +// +// - Child windows with scrolling doesn't work in the node. The child window appears in a normal node, +// and scrolls, but its contents are floating around in the wrong location, and they are not scaled. +// Note that you can put scrolling child windows into "deferred popups" (see next item). +// - Listboxes and combo-boxes only work in nodes with a work-around: deferring the popup calls until after the node drawing is +// completed. Look to the popup-demo for an example. +// - Headers and trees work inside the nodes only with hacks. This is because they attempt to span the "avaialbe width" +// and the nodes can't tell these widgets how wide it is. The work-around is to set up a fake +// table with a static column width, then draw your header and tree widgets in that column. +// - Clickable tabs don't work in nodes. Tabs appear, but you cannot actually change the tab, so they're functionally useless. +// - Editable text areas work, but you have to manually manage disabling the editor shorcuts while typing is detected. +// Look around for the call to ed::EnableShortcuts() for an example. +// - Most of the cool graph widgets can't be used because they are hard-coded in ImGui to spawn tooltips, which don't work. + +# include +# include +# include +# include + +namespace ed = ax::NodeEditor; + +# ifdef _MSC_VER +# define portable_strcpy strcpy_s +# define portable_sprintf sprintf_s +# else +# define portable_strcpy strcpy +# define portable_sprintf sprintf +# endif + +struct Example: + public Application +{ + using Application::Application; + + struct LinkInfo + { + ed::LinkId Id; + ed::PinId InputId; + ed::PinId OutputId; + }; + + void OnStart() override + { + ed::Config config; + config.SettingsFile = "Widgets.json"; + m_Context = ed::CreateEditor(&config); + } + + void OnStop() override + { + ed::DestroyEditor(m_Context); + } + + void OnFrame(float deltaTime) override + { + static bool firstframe = true; // Used to position the nodes on startup + auto& io = ImGui::GetIO(); + + // FPS Counter Ribbon + ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f); + ImGui::Separator(); + + // Node Editor Widget + ed::SetCurrentEditor(m_Context); + ed::Begin("My Editor", ImVec2(0.0, 0.0f)); + int uniqueId = 1; + + + // Basic Widgets Demo ============================================================================================== + auto basic_id = uniqueId++; + ed::BeginNode(basic_id); + ImGui::Text("Basic Widget Demo"); + ed::BeginPin(uniqueId++, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(250, 0)); // Hacky magic number to space out the output pin. + ImGui::SameLine(); + ed::BeginPin(uniqueId++, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + + // Widget Demo from imgui_demo.cpp... + // Normal Button + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + // Checkbox + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + // Radio buttons + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + // Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements (otherwise a Text+SameLine+Button sequence will have the text a little too high by default) + ImGui::AlignTextToFramePadding(); + ImGui::Text("Hold to repeat:"); + ImGui::SameLine(); + + // Arrow buttons with Repeater + static int counter = 0; + float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::PushButtonRepeat(true); + if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } + ImGui::SameLine(0.0f, spacing); + if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } + ImGui::PopButtonRepeat(); + ImGui::SameLine(); + ImGui::Text("%d", counter); + + // The input widgets also require you to manually disable the editor shortcuts so the view doesn't fly around. + // (note that this is a per-frame setting, so it disables it for all text boxes. I left it here so you could find it!) + ed::EnableShortcuts(!io.WantTextInput); + // The input widgets require some guidance on their widths, or else they're very large. (note matching pop at the end). + ImGui::PushItemWidth(200); + static char str1[128] = ""; + ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1)); + + static float f0 = 0.001f; + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f, "%.3f"); + + static float f1 = 1.00f, f2 = 0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + ImGui::PopItemWidth(); + + ed::EndNode(); + if (firstframe) + { + ed::SetNodePosition(basic_id, ImVec2(20, 20)); + } + + // Headers and Trees Demo ======================================================================================================= + // TreeNodes and Headers streatch to the entire remaining work area. To put them in nodes what we need to do is to tell + // ImGui out work area is shorter. We can achieve that right now only by using columns API. + // + // Relevent bugs: https://github.com/thedmd/imgui-node-editor/issues/30 + auto header_id = uniqueId++; + ed::BeginNode(header_id); + ImGui::Text("Tree Widget Demo"); + + // Pins Row + ed::BeginPin(uniqueId++, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(35, 0)); // magic number - Crude & simple way to nudge over the output pin. Consider using layout and springs + ImGui::SameLine(); + ed::BeginPin(uniqueId++, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + + // Tree column startup ------------------------------------------------------------------- + // Push dummy widget to extend node size. Columns do not do that. + float width = 135; // bad magic numbers. used to define width of tree widget + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + ImGui::Dummy(ImVec2(width, 0)); + ImGui::PopStyleVar(); + + // Start columns, but use only first one. + ImGui::BeginColumns("##TreeColumns", 2, + ImGuiOldColumnFlags_NoBorder | + ImGuiOldColumnFlags_NoResize | + ImGuiOldColumnFlags_NoPreserveWidths | + ImGuiOldColumnFlags_NoForceWithinWindow); + + // Adjust column width to match requested one. + ImGui::SetColumnWidth(0, width + + ImGui::GetStyle().WindowPadding.x + + ImGui::GetStyle().ItemSpacing.x); + // End of tree column startup -------------------------------------------------------------- + + // Back to normal ImGui drawing, in our column. + if (ImGui::CollapsingHeader("Open Header")) + { + ImGui::Text("Hello There"); + if (ImGui::TreeNode("Open Tree")) { + static bool OP1_Bool = false; + ImGui::Text("Checked: %s", OP1_Bool ? "true" : "false"); + ImGui::Checkbox("Option 1", &OP1_Bool); + ImGui::TreePop(); + } + } + // Tree Column Shutdown + ImGui::EndColumns(); + ed::EndNode(); // End of Tree Node Demo + + if (firstframe) + { + ed::SetNodePosition(header_id, ImVec2(420, 20)); + } + + // Tool Tip & Pop-up Demo ===================================================================================== + // Tooltips, combo-boxes, drop-down menus need to use a work-around to place the "overlay window" in the canvas. + // To do this, we must defer the popup calls until after we're done drawing the node material. + // + // Relevent bugs: https://github.com/thedmd/imgui-node-editor/issues/48 + auto popup_id = uniqueId++; + ed::BeginNode(popup_id); + ImGui::Text("Tool Tip & Pop-up Demo"); + ed::BeginPin(uniqueId++, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(85, 0)); // Hacky magic number to space out the output pin. + ImGui::SameLine(); + ed::BeginPin(uniqueId++, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + + // Tooltip example + ImGui::Text("Hover over me"); + static bool do_tooltip = false; + do_tooltip = ImGui::IsItemHovered() ? true : false; + ImGui::SameLine(); + ImGui::Text("- or me"); + static bool do_adv_tooltip = false; + do_adv_tooltip = ImGui::IsItemHovered() ? true : false; + + // Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements + // (otherwise a Text+SameLine+Button sequence will have the text a little too high by default) + ImGui::AlignTextToFramePadding(); + ImGui::Text("Option:"); + ImGui::SameLine(); + static char popup_text[128] = "Pick one!"; + static bool do_popup = false; + if (ImGui::Button(popup_text)) { + do_popup = true; // Instead of saying OpenPopup() here, we set this bool, which is used later in the Deferred Pop-up Section + } + ed::EndNode(); + if (firstframe) { + ed::SetNodePosition(popup_id, ImVec2(610, 20)); + } + + // -------------------------------------------------------------------------------------------------- + // Deferred Pop-up Section + + // This entire section needs to be bounded by Suspend/Resume! These calls pop us out of "node canvas coordinates" + // and draw the popups in a reasonable screen location. + ed::Suspend(); + // There is some stately stuff happening here. You call "open popup" exactly once, and this + // causes it to stick open for many frames until the user makes a selection in the popup, or clicks off to dismiss. + // More importantly, this is done inside Suspend(), so it loads the popup with the correct screen coordinates! + if (do_popup) { + ImGui::OpenPopup("popup_button"); // Cause openpopup to stick open. + do_popup = false; // disable bool so that if we click off the popup, it doesn't open the next frame. + } + + // This is the actual popup Gui drawing section. + if (ImGui::BeginPopup("popup_button")) { + // Note: if it weren't for the child window, we would have to PushItemWidth() here to avoid a crash! + ImGui::TextDisabled("Pick One:"); + ImGui::BeginChild("popup_scroller", ImVec2(100, 100), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); + if (ImGui::Button("Option 1")) { + portable_strcpy(popup_text, "Option 1"); + ImGui::CloseCurrentPopup(); // These calls revoke the popup open state, which was set by OpenPopup above. + } + if (ImGui::Button("Option 2")) { + portable_strcpy(popup_text, "Option 2"); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Option 3")) { + portable_strcpy(popup_text, "Option 3"); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Option 4")) { + portable_strcpy(popup_text, "Option 4"); + ImGui::CloseCurrentPopup(); + } + ImGui::EndChild(); + ImGui::EndPopup(); // Note this does not do anything to the popup open/close state. It just terminates the content declaration. + } + + // Handle the simple tooltip + if (do_tooltip) + ImGui::SetTooltip("I am a tooltip"); + + // Handle the advanced tooltip + if (do_adv_tooltip) { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + ed::Resume(); + // End of "Deferred Pop-up section" + + + + // Plot Widgets ========================================================================================= + // Note: most of these plots can't be used in nodes missing, because they spawn tooltips automatically, + // so we can't trap them in our deferred pop-up mechanism. This causes them to fly into a random screen + // location. + auto plot_id = uniqueId++; + ed::BeginNode(plot_id); + ImGui::Text("Plot Demo"); + ed::BeginPin(uniqueId++, ed::PinKind::Input); + ImGui::Text("-> In"); + ed::EndPin(); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(250, 0)); // Hacky magic number to space out the output pin. + ImGui::SameLine(); + ed::BeginPin(uniqueId++, ed::PinKind::Output); + ImGui::Text("Out ->"); + ed::EndPin(); + + ImGui::PushItemWidth(300); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + + + // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, + // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; + char buf[32]; + portable_sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); + + ImGui::PopItemWidth(); + ed::EndNode(); + if (firstframe) { + ed::SetNodePosition(plot_id, ImVec2(850, 20)); + } + // ================================================================================================== + // Link Drawing Section + + for (auto& linkInfo : m_Links) + ed::Link(linkInfo.Id, linkInfo.InputId, linkInfo.OutputId); + + // ================================================================================================== + // Interaction Handling Section + // This was coppied from BasicInteration.cpp. See that file for commented code. + + // Handle creation action --------------------------------------------------------------------------- + if (ed::BeginCreate()) + { + ed::PinId inputPinId, outputPinId; + if (ed::QueryNewLink(&inputPinId, &outputPinId)) + { + if (inputPinId && outputPinId) + { + if (ed::AcceptNewItem()) + { + m_Links.push_back({ ed::LinkId(m_NextLinkId++), inputPinId, outputPinId }); + ed::Link(m_Links.back().Id, m_Links.back().InputId, m_Links.back().OutputId); + } + } + } + } + ed::EndCreate(); + + // Handle deletion action --------------------------------------------------------------------------- + if (ed::BeginDelete()) + { + ed::LinkId deletedLinkId; + while (ed::QueryDeletedLink(&deletedLinkId)) + { + if (ed::AcceptDeletedItem()) + { + for (auto& link : m_Links) + { + if (link.Id == deletedLinkId) + { + m_Links.erase(&link); + break; + } + } + } + } + } + ed::EndDelete(); + + ed::End(); + ed::SetCurrentEditor(nullptr); + firstframe = false; + //ImGui::ShowMetricsWindow(); + //ImGui::ShowDemoWindow(); + } + + ed::EditorContext* m_Context = nullptr; + + ImVector m_Links; // List of live links. It is dynamic unless you want to create read-only view over nodes. + int m_NextLinkId = 100; // Counter to help generate link ids. In real application this will probably based on pointer to user data structure. +}; + +int Main(int argc, char** argv) +{ + Example exampe("Widgets", argc, argv); + + if (exampe.Create()) + return exampe.Run(); + + return 0; +} \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/imgui_bezier_math.h b/cpp/vendor/imgui-node-editor/imgui_bezier_math.h new file mode 100644 index 00000000..85ead39e --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_bezier_math.h @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// VERSION 0.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_BEZIER_MATH_H__ +# define __IMGUI_BEZIER_MATH_H__ +# pragma once + + +//------------------------------------------------------------------------------ +# include "imgui_extra_math.h" + + +//------------------------------------------------------------------------------ +template +struct ImCubicBezierPointsT +{ + T P0; + T P1; + T P2; + T P3; +}; +using ImCubicBezierPoints = ImCubicBezierPointsT; + + +//------------------------------------------------------------------------------ +// Low-level Bezier curve sampling. +template inline T ImLinearBezier(const T& p0, const T& p1, float t); +template inline T ImLinearBezierDt(const T& p0, const T& p1, float t); +template inline T ImQuadraticBezier(const T& p0, const T& p1, const T& p2, float t); +template inline T ImQuadraticBezierDt(const T& p0, const T& p1, const T& p2, float t); +template inline T ImCubicBezier(const T& p0, const T& p1, const T& p2, const T& p3, float t); +template inline T ImCubicBezierDt(const T& p0, const T& p1, const T& p2, const T& p3, float t); + + +// High-level Bezier sampling, automatically collapse to lower level Bezier curves if control points overlap. +template inline T ImCubicBezierSample(const T& p0, const T& p1, const T& p2, const T& p3, float t); +template inline T ImCubicBezierSample(const ImCubicBezierPointsT& curve, float t); +template inline T ImCubicBezierTangent(const T& p0, const T& p1, const T& p2, const T& p3, float t); +template inline T ImCubicBezierTangent(const ImCubicBezierPointsT& curve, float t); + + +// Calculate approximate length of Cubic Bezier curve. +template inline float ImCubicBezierLength(const T& p0, const T& p1, const T& p2, const T& p3); +template inline float ImCubicBezierLength(const ImCubicBezierPointsT& curve); + + +// Splits Cubic Bezier curve into two curves. +template +struct ImCubicBezierSplitResultT +{ + ImCubicBezierPointsT Left; + ImCubicBezierPointsT Right; +}; +using ImCubicBezierSplitResult = ImCubicBezierSplitResultT; + +template inline ImCubicBezierSplitResultT ImCubicBezierSplit(const T& p0, const T& p1, const T& p2, const T& p3, float t); +template inline ImCubicBezierSplitResultT ImCubicBezierSplit(const ImCubicBezierPointsT& curve, float t); + + +// Returns bounding rectangle of Cubic Bezier curve. +inline ImRect ImCubicBezierBoundingRect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3); +inline ImRect ImCubicBezierBoundingRect(const ImCubicBezierPoints& curve); + + +// Project point on Cubic Bezier curve. +struct ImProjectResult +{ + ImVec2 Point; // Point on curve + float Time; // [0 - 1] + float Distance; // Distance to curve +}; + +inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const int subdivisions = 100); +inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImCubicBezierPoints& curve, const int subdivisions = 100); + + +// Calculate intersection between line and a Cubic Bezier curve. +struct ImCubicBezierIntersectResult +{ + int Count; + ImVec2 Points[3]; +}; + +inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& a0, const ImVec2& a1); +inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImCubicBezierPoints& curve, const ImLine& line); + + +// Adaptive Cubic Bezier subdivision. +enum ImCubicBezierSubdivideFlags +{ + ImCubicBezierSubdivide_None = 0, + ImCubicBezierSubdivide_SkipFirst = 1 +}; + +struct ImCubicBezierSubdivideSample +{ + ImVec2 Point; + ImVec2 Tangent; +}; + +using ImCubicBezierSubdivideCallback = void (*)(const ImCubicBezierSubdivideSample& p, void* user_pointer); + +inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None); +inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None); + + +// F has signature void(const ImCubicBezierSubdivideSample& p) +template inline void ImCubicBezierSubdivide(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None); +template inline void ImCubicBezierSubdivide(F& callback, const ImCubicBezierPoints& curve, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None); + +// Fixed step Cubic Bezier subdivision. +struct ImCubicBezierFixedStepSample +{ + float T; + float Length; + ImVec2 Point; + bool BreakSearch; +}; + +using ImCubicBezierFixedStepCallback = void (*)(ImCubicBezierFixedStepSample& sample, void* user_pointer); + +inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f); +inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f); + + +// F has signature void(const ImCubicBezierFixedStepSample& p) +template inline void ImCubicBezierFixedStep(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f); +template inline void ImCubicBezierFixedStep(F& callback, const ImCubicBezierPoints& curve, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f); + + +//------------------------------------------------------------------------------ +# include "imgui_bezier_math.inl" + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_BEZIER_MATH_H__ diff --git a/cpp/vendor/imgui-node-editor/imgui_bezier_math.inl b/cpp/vendor/imgui-node-editor/imgui_bezier_math.inl new file mode 100644 index 00000000..3020bdb4 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_bezier_math.inl @@ -0,0 +1,675 @@ +//------------------------------------------------------------------------------ +// VERSION 0.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_BEZIER_MATH_INL__ +# define __IMGUI_BEZIER_MATH_INL__ +# pragma once + + +//------------------------------------------------------------------------------ +# include "imgui_bezier_math.h" +# include // used in ImCubicBezierFixedStep + + +//------------------------------------------------------------------------------ +template +inline T ImLinearBezier(const T& p0, const T& p1, float t) +{ + return p0 + t * (p1 - p0); +} + +template +inline T ImLinearBezierDt(const T& p0, const T& p1, float t) +{ + IM_UNUSED(t); + + return p1 - p0; +} + +template +inline T ImQuadraticBezier(const T& p0, const T& p1, const T& p2, float t) +{ + const auto a = 1 - t; + + return a * a * p0 + 2 * t * a * p1 + t * t * p2; +} + +template +inline T ImQuadraticBezierDt(const T& p0, const T& p1, const T& p2, float t) +{ + return 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1); +} + +template +inline T ImCubicBezier(const T& p0, const T& p1, const T& p2, const T& p3, float t) +{ + const auto a = 1 - t; + const auto b = a * a * a; + const auto c = t * t * t; + + return b * p0 + 3 * t * a * a * p1 + 3 * t * t * a * p2 + c * p3; +} + +template +inline T ImCubicBezierDt(const T& p0, const T& p1, const T& p2, const T& p3, float t) +{ + const auto a = 1 - t; + const auto b = a * a; + const auto c = t * t; + const auto d = 2 * t * a; + + return -3 * p0 * b + 3 * p1 * (b - d) + 3 * p2 * (d - c) + 3 * p3 * c; +} + +template +inline T ImCubicBezierSample(const T& p0, const T& p1, const T& p2, const T& p3, float t) +{ + const auto cp0_zero = ImLengthSqr(p1 - p0) < 1e-5f; + const auto cp1_zero = ImLengthSqr(p3 - p2) < 1e-5f; + + if (cp0_zero && cp1_zero) + return ImLinearBezier(p0, p3, t); + else if (cp0_zero) + return ImQuadraticBezier(p0, p2, p3, t); + else if (cp1_zero) + return ImQuadraticBezier(p0, p1, p3, t); + else + return ImCubicBezier(p0, p1, p2, p3, t); +} + +template +inline T ImCubicBezierSample(const ImCubicBezierPointsT& curve, float t) +{ + return ImCubicBezierSample(curve.P0, curve.P1, curve.P2, curve.P3, t); +} + +template +inline T ImCubicBezierTangent(const T& p0, const T& p1, const T& p2, const T& p3, float t) +{ + const auto cp0_zero = ImLengthSqr(p1 - p0) < 1e-5f; + const auto cp1_zero = ImLengthSqr(p3 - p2) < 1e-5f; + + if (cp0_zero && cp1_zero) + return ImLinearBezierDt(p0, p3, t); + else if (cp0_zero) + return ImQuadraticBezierDt(p0, p2, p3, t); + else if (cp1_zero) + return ImQuadraticBezierDt(p0, p1, p3, t); + else + return ImCubicBezierDt(p0, p1, p2, p3, t); +} + +template +inline T ImCubicBezierTangent(const ImCubicBezierPointsT& curve, float t) +{ + return ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, t); +} + +template +inline float ImCubicBezierLength(const T& p0, const T& p1, const T& p2, const T& p3) +{ + // Legendre-Gauss abscissae with n=24 (x_i values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x)) + static const float t_values[] = + { + -0.0640568928626056260850430826247450385909f, + 0.0640568928626056260850430826247450385909f, + -0.1911188674736163091586398207570696318404f, + 0.1911188674736163091586398207570696318404f, + -0.3150426796961633743867932913198102407864f, + 0.3150426796961633743867932913198102407864f, + -0.4337935076260451384870842319133497124524f, + 0.4337935076260451384870842319133497124524f, + -0.5454214713888395356583756172183723700107f, + 0.5454214713888395356583756172183723700107f, + -0.6480936519369755692524957869107476266696f, + 0.6480936519369755692524957869107476266696f, + -0.7401241915785543642438281030999784255232f, + 0.7401241915785543642438281030999784255232f, + -0.8200019859739029219539498726697452080761f, + 0.8200019859739029219539498726697452080761f, + -0.8864155270044010342131543419821967550873f, + 0.8864155270044010342131543419821967550873f, + -0.9382745520027327585236490017087214496548f, + 0.9382745520027327585236490017087214496548f, + -0.9747285559713094981983919930081690617411f, + 0.9747285559713094981983919930081690617411f, + -0.9951872199970213601799974097007368118745f, + 0.9951872199970213601799974097007368118745f + }; + + // Legendre-Gauss weights with n=24 (w_i values, defined by a function linked to in the Bezier primer article) + static const float c_values[] = + { + 0.1279381953467521569740561652246953718517f, + 0.1279381953467521569740561652246953718517f, + 0.1258374563468282961213753825111836887264f, + 0.1258374563468282961213753825111836887264f, + 0.1216704729278033912044631534762624256070f, + 0.1216704729278033912044631534762624256070f, + 0.1155056680537256013533444839067835598622f, + 0.1155056680537256013533444839067835598622f, + 0.1074442701159656347825773424466062227946f, + 0.1074442701159656347825773424466062227946f, + 0.0976186521041138882698806644642471544279f, + 0.0976186521041138882698806644642471544279f, + 0.0861901615319532759171852029837426671850f, + 0.0861901615319532759171852029837426671850f, + 0.0733464814110803057340336152531165181193f, + 0.0733464814110803057340336152531165181193f, + 0.0592985849154367807463677585001085845412f, + 0.0592985849154367807463677585001085845412f, + 0.0442774388174198061686027482113382288593f, + 0.0442774388174198061686027482113382288593f, + 0.0285313886289336631813078159518782864491f, + 0.0285313886289336631813078159518782864491f, + 0.0123412297999871995468056670700372915759f, + 0.0123412297999871995468056670700372915759f + }; + + static_assert(sizeof(t_values) / sizeof(*t_values) == sizeof(c_values) / sizeof(*c_values), ""); + + auto arc = [p0, p1, p2, p3](float t) + { + const auto p = ImCubicBezierDt(p0, p1, p2, p3, t); + const auto l = ImLength(p); + return l; + }; + + const auto z = 0.5f; + const auto n = sizeof(t_values) / sizeof(*t_values); + + auto accumulator = 0.0f; + for (size_t i = 0; i < n; ++i) + { + const auto t = z * t_values[i] + z; + accumulator += c_values[i] * arc(t); + } + + return z * accumulator; +} + +template +inline float ImCubicBezierLength(const ImCubicBezierPointsT& curve) +{ + return ImCubicBezierLength(curve.P0, curve.P1, curve.P2, curve.P3); +} + +template +inline ImCubicBezierSplitResultT ImCubicBezierSplit(const T& p0, const T& p1, const T& p2, const T& p3, float t) +{ + const auto z1 = t; + const auto z2 = z1 * z1; + const auto z3 = z1 * z1 * z1; + const auto s1 = z1 - 1; + const auto s2 = s1 * s1; + const auto s3 = s1 * s1 * s1; + + return ImCubicBezierSplitResultT + { + ImCubicBezierPointsT + { + p0, + z1 * p1 - s1 * p0, + z2 * p2 - 2 * z1 * s1 * p1 + s2 * p0, + z3 * p3 - 3 * z2 * s1 * p2 + 3 * z1 * s2 * p1 - s3 * p0 + }, + ImCubicBezierPointsT + { + z3 * p0 - 3 * z2 * s1 * p1 + 3 * z1 * s2 * p2 - s3 * p3, + z2 * p1 - 2 * z1 * s1 * p2 + s2 * p3, + z1 * p2 - s1 * p3, + p3, + } + }; +} + +template +inline ImCubicBezierSplitResultT ImCubicBezierSplit(const ImCubicBezierPointsT& curve, float t) +{ + return ImCubicBezierSplit(curve.P0, curve.P1, curve.P2, curve.P3, t); +} + +inline ImRect ImCubicBezierBoundingRect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3) +{ + auto a = 3 * p3 - 9 * p2 + 9 * p1 - 3 * p0; + auto b = 6 * p0 - 12 * p1 + 6 * p2; + auto c = 3 * p1 - 3 * p0; + auto delta_squared = ImMul(b, b) - 4 * ImMul(a, c); + + auto tl = ImMin(p0, p3); + auto rb = ImMax(p0, p3); + +# define IM_VEC2_INDEX(v, i) *(&v.x + i) + + for (int i = 0; i < 2; ++i) + { + if (IM_VEC2_INDEX(a, i) == 0.0f) + continue; + + if (IM_VEC2_INDEX(delta_squared, i) >= 0) + { + auto delta = ImSqrt(IM_VEC2_INDEX(delta_squared, i)); + + auto t0 = (-IM_VEC2_INDEX(b, i) + delta) / (2 * IM_VEC2_INDEX(a, i)); + if (t0 > 0 && t0 < 1) + { + auto p = ImCubicBezier(IM_VEC2_INDEX(p0, i), IM_VEC2_INDEX(p1, i), IM_VEC2_INDEX(p2, i), IM_VEC2_INDEX(p3, i), t0); + IM_VEC2_INDEX(tl, i) = ImMin(IM_VEC2_INDEX(tl, i), p); + IM_VEC2_INDEX(rb, i) = ImMax(IM_VEC2_INDEX(rb, i), p); + } + + auto t1 = (-IM_VEC2_INDEX(b, i) - delta) / (2 * IM_VEC2_INDEX(a, i)); + if (t1 > 0 && t1 < 1) + { + auto p = ImCubicBezier(IM_VEC2_INDEX(p0, i), IM_VEC2_INDEX(p1, i), IM_VEC2_INDEX(p2, i), IM_VEC2_INDEX(p3, i), t1); + IM_VEC2_INDEX(tl, i) = ImMin(IM_VEC2_INDEX(tl, i), p); + IM_VEC2_INDEX(rb, i) = ImMax(IM_VEC2_INDEX(rb, i), p); + } + } + } + +# undef IM_VEC2_INDEX + + return ImRect(tl, rb); +} + +inline ImRect ImCubicBezierBoundingRect(const ImCubicBezierPoints& curve) +{ + return ImCubicBezierBoundingRect(curve.P0, curve.P1, curve.P2, curve.P3); +} + +inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& point, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const int subdivisions) +{ + // http://pomax.github.io/bezierinfo/#projections + + const float epsilon = 1e-5f; + const float fixed_step = 1.0f / static_cast(subdivisions - 1); + + ImProjectResult result; + result.Point = point; + result.Time = 0.0f; + result.Distance = FLT_MAX; + + // Step 1: Coarse check + for (int i = 0; i < subdivisions; ++i) + { + auto t = i * fixed_step; + auto p = ImCubicBezier(p0, p1, p2, p3, t); + auto s = point - p; + auto d = ImDot(s, s); + + if (d < result.Distance) + { + result.Point = p; + result.Time = t; + result.Distance = d; + } + } + + if (result.Time == 0.0f || ImFabs(result.Time - 1.0f) <= epsilon) + { + result.Distance = ImSqrt(result.Distance); + return result; + } + + // Step 2: Fine check + auto left = result.Time - fixed_step; + auto right = result.Time + fixed_step; + auto step = fixed_step * 0.1f; + + for (auto t = left; t < right + step; t += step) + { + auto p = ImCubicBezier(p0, p1, p2, p3, t); + auto s = point - p; + auto d = ImDot(s, s); + + if (d < result.Distance) + { + result.Point = p; + result.Time = t; + result.Distance = d; + } + } + + result.Distance = ImSqrt(result.Distance); + + return result; +} + +inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImCubicBezierPoints& curve, const int subdivisions) +{ + return ImProjectOnCubicBezier(p, curve.P0, curve.P1, curve.P2, curve.P3, subdivisions); +} + +inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& a0, const ImVec2& a1) +{ + auto cubic_roots = [](float a, float b, float c, float d, float* roots) -> int + { + int count = 0; + + auto sign = [](float x) -> float { return x < 0 ? -1.0f : 1.0f; }; + + auto A = b / a; + auto B = c / a; + auto C = d / a; + + auto Q = (3 * B - ImPow(A, 2)) / 9; + auto R = (9 * A * B - 27 * C - 2 * ImPow(A, 3)) / 54; + auto D = ImPow(Q, 3) + ImPow(R, 2); // polynomial discriminant + + if (D >= 0) // complex or duplicate roots + { + auto S = sign(R + ImSqrt(D)) * ImPow(ImFabs(R + ImSqrt(D)), (1.0f / 3.0f)); + auto T = sign(R - ImSqrt(D)) * ImPow(ImFabs(R - ImSqrt(D)), (1.0f / 3.0f)); + + roots[0] = -A / 3 + (S + T); // real root + roots[1] = -A / 3 - (S + T) / 2; // real part of complex root + roots[2] = -A / 3 - (S + T) / 2; // real part of complex root + auto Im = ImFabs(ImSqrt(3) * (S - T) / 2); // complex part of root pair + + // discard complex roots + if (Im != 0) + count = 1; + else + count = 3; + } + else // distinct real roots + { + auto th = ImAcos(R / ImSqrt(-ImPow(Q, 3))); + + roots[0] = 2 * ImSqrt(-Q) * ImCos(th / 3) - A / 3; + roots[1] = 2 * ImSqrt(-Q) * ImCos((th + 2 * IM_PI) / 3) - A / 3; + roots[2] = 2 * ImSqrt(-Q) * ImCos((th + 4 * IM_PI) / 3) - A / 3; + + count = 3; + } + + return count; + }; + + // https://github.com/kaishiqi/Geometric-Bezier/blob/master/GeometricBezier/src/kaishiqi/geometric/intersection/Intersection.as + // + // Start with Bezier using Bernstein polynomials for weighting functions: + // (1-t^3)P0 + 3t(1-t)^2P1 + 3t^2(1-t)P2 + t^3P3 + // + // Expand and collect terms to form linear combinations of original Bezier + // controls. This ends up with a vector cubic in t: + // (-P0+3P1-3P2+P3)t^3 + (3P0-6P1+3P2)t^2 + (-3P0+3P1)t + P0 + // /\ /\ /\ /\ + // || || || || + // c3 c2 c1 c0 + + // Calculate the coefficients + auto c3 = -p0 + 3 * p1 - 3 * p2 + p3; + auto c2 = 3 * p0 - 6 * p1 + 3 * p2; + auto c1 = -3 * p0 + 3 * p1; + auto c0 = p0; + + // Convert line to normal form: ax + by + c = 0 + auto a = a1.y - a0.y; + auto b = a0.x - a1.x; + auto c = a0.x * (a0.y - a1.y) + a0.y * (a1.x - a0.x); + + // Rotate each cubic coefficient using line for new coordinate system? + // Find roots of rotated cubic + float roots[3]; + auto rootCount = cubic_roots( + a * c3.x + b * c3.y, + a * c2.x + b * c2.y, + a * c1.x + b * c1.y, + a * c0.x + b * c0.y + c, + roots); + + // Any roots in closed interval [0,1] are intersections on Bezier, but + // might not be on the line segment. + // Find intersections and calculate point coordinates + + auto min = ImMin(a0, a1); + auto max = ImMax(a0, a1); + + ImCubicBezierIntersectResult result; + auto points = result.Points; + + for (int i = 0; i < rootCount; ++i) + { + auto root = roots[i]; + + if (0 <= root && root <= 1) + { + // We're within the Bezier curve + // Find point on Bezier + auto p = ImCubicBezier(p0, p1, p2, p3, root); + + // See if point is on line segment + // Had to make special cases for vertical and horizontal lines due + // to slight errors in calculation of p00 + if (a0.x == a1.x) + { + if (min.y <= p.y && p.y <= max.y) + *points++ = p; + } + else if (a0.y == a1.y) + { + if (min.x <= p.x && p.x <= max.x) + *points++ = p; + } + else if (p.x >= min.x && p.y >= min.y && p.x <= max.x && p.y <= max.y) + { + *points++ = p; + } + } + } + + result.Count = static_cast(points - result.Points); + + return result; +} + +inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImCubicBezierPoints& curve, const ImLine& line) +{ + return ImCubicBezierLineIntersect(curve.P0, curve.P1, curve.P2, curve.P3, line.A, line.B); +} + +inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol, ImCubicBezierSubdivideFlags flags) +{ + return ImCubicBezierSubdivide(callback, user_pointer, ImCubicBezierPoints{ p0, p1, p2, p3 }, tess_tol, flags); +} + +inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float tess_tol, ImCubicBezierSubdivideFlags flags) +{ + struct Tesselator + { + ImCubicBezierSubdivideCallback Callback; + void* UserPointer; + float TesselationTollerance; + ImCubicBezierSubdivideFlags Flags; + + void Commit(const ImVec2& p, const ImVec2& t) + { + ImCubicBezierSubdivideSample sample; + sample.Point = p; + sample.Tangent = t; + Callback(sample, UserPointer); + } + + void Subdivide(const ImCubicBezierPoints& curve, int level = 0) + { + float dx = curve.P3.x - curve.P0.x; + float dy = curve.P3.y - curve.P0.y; + float d2 = ((curve.P1.x - curve.P3.x) * dy - (curve.P1.y - curve.P3.y) * dx); + float d3 = ((curve.P2.x - curve.P3.x) * dy - (curve.P2.y - curve.P3.y) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < TesselationTollerance * (dx * dx + dy * dy)) + { + Commit(curve.P3, ImCubicBezierTangent(curve, 1.0f)); + } + else if (level < 10) + { + const auto p12 = (curve.P0 + curve.P1) * 0.5f; + const auto p23 = (curve.P1 + curve.P2) * 0.5f; + const auto p34 = (curve.P2 + curve.P3) * 0.5f; + const auto p123 = (p12 + p23) * 0.5f; + const auto p234 = (p23 + p34) * 0.5f; + const auto p1234 = (p123 + p234) * 0.5f; + + Subdivide(ImCubicBezierPoints { curve.P0, p12, p123, p1234 }, level + 1); + Subdivide(ImCubicBezierPoints { p1234, p234, p34, curve.P3 }, level + 1); + } + } + }; + + if (tess_tol < 0) + tess_tol = 1.118f; // sqrtf(1.25f) + + Tesselator tesselator; + tesselator.Callback = callback; + tesselator.UserPointer = user_pointer; + tesselator.TesselationTollerance = tess_tol * tess_tol; + tesselator.Flags = flags; + + if (!(tesselator.Flags & ImCubicBezierSubdivide_SkipFirst)) + tesselator.Commit(curve.P0, ImCubicBezierTangent(curve, 0.0f)); + + tesselator.Subdivide(curve, 0); +} + +template inline void ImCubicBezierSubdivide(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol, ImCubicBezierSubdivideFlags flags) +{ + auto handler = [](const ImCubicBezierSubdivideSample& p, void* user_pointer) + { + auto& callback = *reinterpret_cast(user_pointer); + callback(p); + }; + + ImCubicBezierSubdivide(handler, &callback, ImCubicBezierPoints{ p0, p1, p2, p3 }, tess_tol, flags); +} + +template inline void ImCubicBezierSubdivide(F& callback, const ImCubicBezierPoints& curve, float tess_tol, ImCubicBezierSubdivideFlags flags) +{ + auto handler = [](const ImCubicBezierSubdivideSample& p, void* user_pointer) + { + auto& callback = *reinterpret_cast(user_pointer); + callback(p); + }; + + ImCubicBezierSubdivide(handler, &callback, curve, tess_tol, flags); +} + +inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot, float max_value_error, float max_t_error) +{ + if (step <= 0.0f || !callback || max_value_error <= 0 || max_t_error <= 0) + return; + + ImCubicBezierFixedStepSample sample; + sample.T = 0.0f; + sample.Length = 0.0f; + sample.Point = p0; + sample.BreakSearch = false; + + callback(sample, user_pointer); + if (sample.BreakSearch) + return; + + const auto total_length = ImCubicBezierLength(p0, p1, p2, p3); + const auto point_count = static_cast(total_length / step) + (overshoot ? 2 : 1); + const auto t_min = 0.0f; + const auto t_max = step * point_count / total_length; + const auto t_0 = (t_min + t_max) * 0.5f; + + // #todo: replace map with ImVector + binary search + std::map cache; + for (int point_index = 1; point_index < point_count; ++point_index) + { + const auto targetLength = point_index * step; + + float t_start = t_min; + float t_end = t_max; + float t = t_0; + + float t_best = t; + float error_best = total_length; + + while (true) + { + auto cacheIt = cache.find(t); + if (cacheIt == cache.end()) + { + const auto front = ImCubicBezierSplit(p0, p1, p2, p3, t).Left; + const auto split_length = ImCubicBezierLength(front); + + cacheIt = cache.emplace(t, split_length).first; + } + + const auto length = cacheIt->second; + const auto error = targetLength - length; + + if (error < error_best) + { + error_best = error; + t_best = t; + } + + if (ImFabs(error) <= max_value_error || ImFabs(t_start - t_end) <= max_t_error) + { + sample.T = t; + sample.Length = length; + sample.Point = ImCubicBezier(p0, p1, p2, p3, t); + + callback(sample, user_pointer); + if (sample.BreakSearch) + return; + + break; + } + else if (error < 0.0f) + t_end = t; + else // if (error > 0.0f) + t_start = t; + + t = (t_start + t_end) * 0.5f; + } + } +} + +inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float step, bool overshoot, float max_value_error, float max_t_error) +{ + ImCubicBezierFixedStep(callback, user_pointer, curve.P0, curve.P1, curve.P2, curve.P3, step, overshoot, max_value_error, max_t_error); +} + +// F has signature void(const ImCubicBezierFixedStepSample& p) +template +inline void ImCubicBezierFixedStep(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot, float max_value_error, float max_t_error) +{ + auto handler = [](ImCubicBezierFixedStepSample& sample, void* user_pointer) + { + auto& callback = *reinterpret_cast(user_pointer); + callback(sample); + }; + + ImCubicBezierFixedStep(handler, &callback, p0, p1, p2, p3, step, overshoot, max_value_error, max_t_error); +} + +template +inline void ImCubicBezierFixedStep(F& callback, const ImCubicBezierPoints& curve, float step, bool overshoot, float max_value_error, float max_t_error) +{ + auto handler = [](ImCubicBezierFixedStepSample& sample, void* user_pointer) + { + auto& callback = *reinterpret_cast(user_pointer); + callback(sample); + }; + + ImCubicBezierFixedStep(handler, &callback, curve.P0, curve.P1, curve.P2, curve.P3, step, overshoot, max_value_error, max_t_error); +} + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_BEZIER_MATH_INL__ diff --git a/cpp/vendor/imgui-node-editor/imgui_canvas.cpp b/cpp/vendor/imgui-node-editor/imgui_canvas.cpp new file mode 100644 index 00000000..2010ee39 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_canvas.cpp @@ -0,0 +1,574 @@ +# ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +# endif +# include "imgui_canvas.h" +# include + +// https://stackoverflow.com/a/36079786 +# define DECLARE_HAS_MEMBER(__trait_name__, __member_name__) \ + \ + template \ + class __trait_name__ \ + { \ + using check_type = ::std::remove_const_t<__boost_has_member_T__>; \ + struct no_type {char x[2];}; \ + using yes_type = char; \ + \ + struct base { void __member_name__() {}}; \ + struct mixin : public base, public check_type {}; \ + \ + template struct aux {}; \ + \ + template static no_type test(aux<&U::__member_name__>*); \ + template static yes_type test(...); \ + \ + public: \ + \ + static constexpr bool value = (sizeof(yes_type) == sizeof(test(0))); \ + } + +// Special sentinel value. This needs to be unique, so allow it to be overridden in the user's ImGui config +# ifndef ImDrawCallback_ImCanvas +# define ImDrawCallback_ImCanvas (ImDrawCallback)(-2) +# endif + +namespace ImCanvasDetails { + +DECLARE_HAS_MEMBER(HasFringeScale, _FringeScale); + +struct FringeScaleRef +{ + // Overload is present when ImDrawList does have _FringeScale member variable. + template + static float& Get(typename std::enable_if::value, T>::type* drawList) + { + return drawList->_FringeScale; + } + + // Overload is present when ImDrawList does not have _FringeScale member variable. + template + static float& Get(typename std::enable_if::value, T>::type*) + { + static float placeholder = 1.0f; + return placeholder; + } +}; + +DECLARE_HAS_MEMBER(HasVtxCurrentOffset, _VtxCurrentOffset); + +struct VtxCurrentOffsetRef +{ + // Overload is present when ImDrawList does have _FringeScale member variable. + template + static unsigned int& Get(typename std::enable_if::value, T>::type* drawList) + { + return drawList->_VtxCurrentOffset; + } + + // Overload is present when ImDrawList does not have _FringeScale member variable. + template + static unsigned int& Get(typename std::enable_if::value, T>::type* drawList) + { + return drawList->_CmdHeader.VtxOffset; + } +}; + +} // namespace ImCanvasDetails + +// Returns a reference to _FringeScale extension to ImDrawList +// +// If ImDrawList does not have _FringeScale a placeholder is returned. +static inline float& ImFringeScaleRef(ImDrawList* drawList) +{ + using namespace ImCanvasDetails; + return FringeScaleRef::Get(drawList); +} + +static inline unsigned int& ImVtxOffsetRef(ImDrawList* drawList) +{ + using namespace ImCanvasDetails; + return VtxCurrentOffsetRef::Get(drawList); +} + +static inline ImVec2 ImSelectPositive(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x > 0.0f ? lhs.x : rhs.x, lhs.y > 0.0f ? lhs.y : rhs.y); } + +bool ImGuiEx::Canvas::Begin(const char* id, const ImVec2& size) +{ + return Begin(ImGui::GetID(id), size); +} + +bool ImGuiEx::Canvas::Begin(ImGuiID id, const ImVec2& size) +{ + IM_ASSERT(m_InBeginEnd == false); + + m_WidgetPosition = ImGui::GetCursorScreenPos(); + m_WidgetSize = ImSelectPositive(size, ImGui::GetContentRegionAvail()); + m_WidgetRect = ImRect(m_WidgetPosition, m_WidgetPosition + m_WidgetSize); + m_DrawList = ImGui::GetWindowDrawList(); + + UpdateViewTransformPosition(); + +# if IMGUI_VERSION_NUM > 18415 + if (ImGui::IsClippedEx(m_WidgetRect, id)) + return false; +# else + if (ImGui::IsClippedEx(m_WidgetRect, id, false)) + return false; +# endif + + // Save current channel, so we can assert when user + // call canvas API with different one. + m_ExpectedChannel = m_DrawList->_Splitter._Current; + + // #debug: Canvas content. + //m_DrawList->AddRectFilled(m_StartPos, m_StartPos + m_CurrentSize, IM_COL32(0, 0, 0, 64)); + //m_DrawList->AddRect(m_WidgetRect.Min, m_WidgetRect.Max, IM_COL32(255, 0, 255, 64)); + + ImGui::SetCursorScreenPos(ImVec2(0.0f, 0.0f)); + +# if IMGUI_EX_CANVAS_DEFERED() + m_Ranges.resize(0); +# endif + + SaveInputState(); + SaveViewportState(); + + // Record cursor max to prevent scrollbars from appearing. + m_WindowCursorMaxBackup = ImGui::GetCurrentWindow()->DC.CursorMaxPos; + + EnterLocalSpace(); + +# if IMGUI_VERSION_NUM >= 18967 + ImGui::SetNextItemAllowOverlap(); +# endif + + // Emit dummy widget matching bounds of the canvas. + ImGui::SetCursorScreenPos(m_ViewRect.Min); + ImGui::Dummy(m_ViewRect.GetSize()); + + ImGui::SetCursorScreenPos(ImVec2(0.0f, 0.0f)); + + m_InBeginEnd = true; + + return true; +} + +void ImGuiEx::Canvas::End() +{ + // If you're here your call to Begin() returned false, + // or Begin() wasn't called at all. + IM_ASSERT(m_InBeginEnd == true); + + // If you're here, please make sure you do not interleave + // channel splitter with canvas. + // Always call canvas function with using same channel. + IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel); + + //auto& io = ImGui::GetIO(); + + // Check: Unmatched calls to Suspend() / Resume(). Please check your code. + IM_ASSERT(m_SuspendCounter == 0); + + LeaveLocalSpace(); + + ImGui::GetCurrentWindow()->DC.CursorMaxPos = m_WindowCursorMaxBackup; + +# if IMGUI_VERSION_NUM < 18967 + ImGui::SetItemAllowOverlap(); +# endif + + // Emit dummy widget matching bounds of the canvas. + ImGui::SetCursorScreenPos(m_WidgetPosition); + ImGui::Dummy(m_WidgetSize); + + // #debug: Rect around canvas. Content should be inside these bounds. + //m_DrawList->AddRect(m_WidgetPosition - ImVec2(1.0f, 1.0f), m_WidgetPosition + m_WidgetSize + ImVec2(1.0f, 1.0f), IM_COL32(196, 0, 0, 255)); + + m_InBeginEnd = false; +} + +void ImGuiEx::Canvas::SetView(const ImVec2& origin, float scale) +{ + SetView(CanvasView(origin, scale)); +} + +void ImGuiEx::Canvas::SetView(const CanvasView& view) +{ + if (m_InBeginEnd) + LeaveLocalSpace(); + + if (m_View.Origin.x != view.Origin.x || m_View.Origin.y != view.Origin.y) + { + m_View.Origin = view.Origin; + + UpdateViewTransformPosition(); + } + + if (m_View.Scale != view.Scale) + { + m_View.Scale = view.Scale; + m_View.InvScale = view.InvScale; + } + + if (m_InBeginEnd) + EnterLocalSpace(); +} + +void ImGuiEx::Canvas::CenterView(const ImVec2& canvasPoint) +{ + auto view = CalcCenterView(canvasPoint); + SetView(view); +} + +ImGuiEx::CanvasView ImGuiEx::Canvas::CalcCenterView(const ImVec2& canvasPoint) const +{ + auto localCenter = ToLocal(m_WidgetPosition + m_WidgetSize * 0.5f); + auto localOffset = canvasPoint - localCenter; + auto offset = FromLocalV(localOffset); + + return CanvasView{ m_View.Origin - offset, m_View.Scale }; +} + +void ImGuiEx::Canvas::CenterView(const ImRect& canvasRect) +{ + auto view = CalcCenterView(canvasRect); + + SetView(view); +} + +ImGuiEx::CanvasView ImGuiEx::Canvas::CalcCenterView(const ImRect& canvasRect) const +{ + auto canvasRectSize = canvasRect.GetSize(); + + if (canvasRectSize.x <= 0.0f || canvasRectSize.y <= 0.0f) + return View(); + + auto widgetAspectRatio = m_WidgetSize.y > 0.0f ? m_WidgetSize.x / m_WidgetSize.y : 0.0f; + auto canvasRectAspectRatio = canvasRectSize.y > 0.0f ? canvasRectSize.x / canvasRectSize.y : 0.0f; + + if (widgetAspectRatio <= 0.0f || canvasRectAspectRatio <= 0.0f) + return View(); + + auto newOrigin = m_View.Origin; + auto newScale = m_View.Scale; + if (canvasRectAspectRatio > widgetAspectRatio) + { + // width span across view + newScale = m_WidgetSize.x / canvasRectSize.x; + newOrigin = canvasRect.Min * -newScale; + newOrigin.y += (m_WidgetSize.y - canvasRectSize.y * newScale) * 0.5f; + } + else + { + // height span across view + newScale = m_WidgetSize.y / canvasRectSize.y; + newOrigin = canvasRect.Min * -newScale; + newOrigin.x += (m_WidgetSize.x - canvasRectSize.x * newScale) * 0.5f; + } + + return CanvasView{ newOrigin, newScale }; +} + +void ImGuiEx::Canvas::Suspend() +{ + // If you're here, please make sure you do not interleave + // channel splitter with canvas. + // Always call canvas function with using same channel. + IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel); + + if (m_SuspendCounter == 0) + LeaveLocalSpace(); + + ++m_SuspendCounter; +} + +void ImGuiEx::Canvas::Resume() +{ + // If you're here, please make sure you do not interleave + // channel splitter with canvas. + // Always call canvas function with using same channel. + IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel); + + // Check: Number of calls to Resume() do not match calls to Suspend(). Please check your code. + IM_ASSERT(m_SuspendCounter > 0); + if (--m_SuspendCounter == 0) + EnterLocalSpace(); +} + +ImVec2 ImGuiEx::Canvas::FromLocal(const ImVec2& point) const +{ + return point * m_View.Scale + m_ViewTransformPosition; +} + +ImVec2 ImGuiEx::Canvas::FromLocal(const ImVec2& point, const CanvasView& view) const +{ + return point * view.Scale + view.Origin + m_WidgetPosition; +} + +ImVec2 ImGuiEx::Canvas::FromLocalV(const ImVec2& vector) const +{ + return vector * m_View.Scale; +} + +ImVec2 ImGuiEx::Canvas::FromLocalV(const ImVec2& vector, const CanvasView& view) const +{ + return vector * view.Scale; +} + +ImVec2 ImGuiEx::Canvas::ToLocal(const ImVec2& point) const +{ + return (point - m_ViewTransformPosition) * m_View.InvScale; +} + +ImVec2 ImGuiEx::Canvas::ToLocal(const ImVec2& point, const CanvasView& view) const +{ + return (point - view.Origin - m_WidgetPosition) * view.InvScale; +} + +ImVec2 ImGuiEx::Canvas::ToLocalV(const ImVec2& vector) const +{ + return vector * m_View.InvScale; +} + +ImVec2 ImGuiEx::Canvas::ToLocalV(const ImVec2& vector, const CanvasView& view) const +{ + return vector * view.InvScale; +} + +ImRect ImGuiEx::Canvas::CalcViewRect(const CanvasView& view) const +{ + ImRect result; + result.Min = ImVec2(-view.Origin.x, -view.Origin.y) * view.InvScale; + result.Max = (m_WidgetSize - view.Origin) * view.InvScale; + return result; +} + +void ImGuiEx::Canvas::UpdateViewTransformPosition() +{ + m_ViewTransformPosition = m_View.Origin + m_WidgetPosition; +} + +void ImGuiEx::Canvas::SaveInputState() +{ + auto& io = ImGui::GetIO(); + m_MousePosBackup = io.MousePos; + m_MousePosPrevBackup = io.MousePosPrev; + for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i) + m_MouseClickedPosBackup[i] = io.MouseClickedPos[i]; +} + +void ImGuiEx::Canvas::RestoreInputState() +{ + auto& io = ImGui::GetIO(); + io.MousePos = m_MousePosBackup; + io.MousePosPrev = m_MousePosPrevBackup; + for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i) + io.MouseClickedPos[i] = m_MouseClickedPosBackup[i]; +} + +void ImGuiEx::Canvas::SaveViewportState() +{ +# if defined(IMGUI_HAS_VIEWPORT) + auto window = ImGui::GetCurrentWindow(); + auto viewport = ImGui::GetWindowViewport(); + + m_WindowPosBackup = window->Pos; + m_ViewportPosBackup = viewport->Pos; + m_ViewportSizeBackup = viewport->Size; +# if IMGUI_VERSION_NUM > 18002 + m_ViewportWorkPosBackup = viewport->WorkPos; + m_ViewportWorkSizeBackup = viewport->WorkSize; +# else + m_ViewportWorkOffsetMinBackup = viewport->WorkOffsetMin; + m_ViewportWorkOffsetMaxBackup = viewport->WorkOffsetMax; +# endif +# endif +} + +void ImGuiEx::Canvas::RestoreViewportState() +{ +# if defined(IMGUI_HAS_VIEWPORT) + auto window = ImGui::GetCurrentWindow(); + auto viewport = ImGui::GetWindowViewport(); + + window->Pos = m_WindowPosBackup; + viewport->Pos = m_ViewportPosBackup; + viewport->Size = m_ViewportSizeBackup; +# if IMGUI_VERSION_NUM > 18002 + viewport->WorkPos = m_ViewportWorkPosBackup; + viewport->WorkSize = m_ViewportWorkSizeBackup; +# else + viewport->WorkOffsetMin = m_ViewportWorkOffsetMinBackup; + viewport->WorkOffsetMax = m_ViewportWorkOffsetMaxBackup; +# endif +# endif +} + +void ImGuiEx::Canvas::EnterLocalSpace() +{ + // Prepare ImDrawList for drawing in local coordinate system: + // - determine visible part of the canvas + // - start unique draw command + // - add clip rect matching canvas size + // - record current command index + // - record current vertex write index + + // Determine visible part of the canvas. Make it before + // adding new command, to avoid round rip where command + // is removed in PopClipRect() and added again next PushClipRect(). + ImGui::PushClipRect(m_WidgetPosition, m_WidgetPosition + m_WidgetSize, true); + auto clipped_clip_rect = m_DrawList->_ClipRectStack.back(); + ImGui::PopClipRect(); + +# if IMGUI_EX_CANVAS_DEFERED() + m_Ranges.resize(m_Ranges.Size + 1); + m_CurrentRange = &m_Ranges.back(); + m_CurrentRange->BeginComandIndex = ImMax(m_DrawList->CmdBuffer.Size, 0); + m_CurrentRange->BeginVertexIndex = m_DrawList->_VtxCurrentIdx + ImVtxOffsetRef(m_DrawList); +# endif + m_DrawListCommadBufferSize = ImMax(m_DrawList->CmdBuffer.Size, 0); + m_DrawListStartVertexIndex = m_DrawList->_VtxCurrentIdx + ImVtxOffsetRef(m_DrawList); + + // Make sure we do not share draw command with anyone. We don't want to mess + // with someones clip rectangle. + + // #FIXME: + // This condition is not enough to avoid when user choose + // to use channel splitter. + // + // To deal with Suspend()/Resume() calls empty draw command + // is always added then splitter is active. Otherwise + // channel merger will collapse our draw command one with + // different clip rectangle. + // + // More investigation is needed. To get to the bottom of this. + if ((!m_DrawList->CmdBuffer.empty() && m_DrawList->CmdBuffer.back().ElemCount > 0) || m_DrawList->_Splitter._Count > 1) + m_DrawList->AddCallback(ImDrawCallback_ImCanvas, nullptr); + + m_DrawListFirstCommandIndex = ImMax(m_DrawList->CmdBuffer.Size - 1, 0); + +# if defined(IMGUI_HAS_VIEWPORT) + auto window = ImGui::GetCurrentWindow(); + window->Pos = ImVec2(0.0f, 0.0f); + + auto viewport_min = m_ViewportPosBackup; + auto viewport_max = m_ViewportPosBackup + m_ViewportSizeBackup; + + viewport_min.x = (viewport_min.x - m_ViewTransformPosition.x) * m_View.InvScale; + viewport_min.y = (viewport_min.y - m_ViewTransformPosition.y) * m_View.InvScale; + viewport_max.x = (viewport_max.x - m_ViewTransformPosition.x) * m_View.InvScale; + viewport_max.y = (viewport_max.y - m_ViewTransformPosition.y) * m_View.InvScale; + + auto viewport = ImGui::GetWindowViewport(); + viewport->Pos = viewport_min; + viewport->Size = viewport_max - viewport_min; + +# if IMGUI_VERSION_NUM > 18002 + viewport->WorkPos = m_ViewportWorkPosBackup * m_View.InvScale; + viewport->WorkSize = m_ViewportWorkSizeBackup * m_View.InvScale; +# else + viewport->WorkOffsetMin = m_ViewportWorkOffsetMinBackup * m_View.InvScale; + viewport->WorkOffsetMax = m_ViewportWorkOffsetMaxBackup * m_View.InvScale; +# endif +# endif + + // Clip rectangle in parent canvas space and move it to local space. + clipped_clip_rect.x = (clipped_clip_rect.x - m_ViewTransformPosition.x) * m_View.InvScale; + clipped_clip_rect.y = (clipped_clip_rect.y - m_ViewTransformPosition.y) * m_View.InvScale; + clipped_clip_rect.z = (clipped_clip_rect.z - m_ViewTransformPosition.x) * m_View.InvScale; + clipped_clip_rect.w = (clipped_clip_rect.w - m_ViewTransformPosition.y) * m_View.InvScale; + ImGui::PushClipRect(ImVec2(clipped_clip_rect.x, clipped_clip_rect.y), ImVec2(clipped_clip_rect.z, clipped_clip_rect.w), false); + + // Transform mouse position to local space. + auto& io = ImGui::GetIO(); + io.MousePos = (m_MousePosBackup - m_ViewTransformPosition) * m_View.InvScale; + io.MousePosPrev = (m_MousePosPrevBackup - m_ViewTransformPosition) * m_View.InvScale; + for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i) + io.MouseClickedPos[i] = (m_MouseClickedPosBackup[i] - m_ViewTransformPosition) * m_View.InvScale; + + m_ViewRect = CalcViewRect(m_View);; + + auto& fringeScale = ImFringeScaleRef(m_DrawList); + m_LastFringeScale = fringeScale; + fringeScale *= m_View.InvScale; +} + +void ImGuiEx::Canvas::LeaveLocalSpace() +{ + IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel); + +# if IMGUI_EX_CANVAS_DEFERED() + IM_ASSERT(m_CurrentRange != nullptr); + + m_CurrentRange->EndVertexIndex = m_DrawList->_VtxCurrentIdx + ImVtxOffsetRef(m_DrawList); + m_CurrentRange->EndCommandIndex = m_DrawList->CmdBuffer.size(); + if (m_CurrentRange->BeginVertexIndex == m_CurrentRange->EndVertexIndex) + { + // Drop empty range + m_Ranges.resize(m_Ranges.Size - 1); + } + m_CurrentRange = nullptr; +# endif + + // Move vertices to screen space. + auto vertex = m_DrawList->VtxBuffer.Data + m_DrawListStartVertexIndex; + auto vertexEnd = m_DrawList->VtxBuffer.Data + m_DrawList->_VtxCurrentIdx + ImVtxOffsetRef(m_DrawList); + + // If canvas view is not scaled take a faster path. + if (m_View.Scale != 1.0f) + { + while (vertex < vertexEnd) + { + vertex->pos.x = vertex->pos.x * m_View.Scale + m_ViewTransformPosition.x; + vertex->pos.y = vertex->pos.y * m_View.Scale + m_ViewTransformPosition.y; + ++vertex; + } + + // Move clip rectangles to screen space. + for (int i = m_DrawListFirstCommandIndex; i < m_DrawList->CmdBuffer.size(); ++i) + { + auto& command = m_DrawList->CmdBuffer[i]; + command.ClipRect.x = command.ClipRect.x * m_View.Scale + m_ViewTransformPosition.x; + command.ClipRect.y = command.ClipRect.y * m_View.Scale + m_ViewTransformPosition.y; + command.ClipRect.z = command.ClipRect.z * m_View.Scale + m_ViewTransformPosition.x; + command.ClipRect.w = command.ClipRect.w * m_View.Scale + m_ViewTransformPosition.y; + } + } + else + { + while (vertex < vertexEnd) + { + vertex->pos.x = vertex->pos.x + m_ViewTransformPosition.x; + vertex->pos.y = vertex->pos.y + m_ViewTransformPosition.y; + ++vertex; + } + + // Move clip rectangles to screen space. + for (int i = m_DrawListFirstCommandIndex; i < m_DrawList->CmdBuffer.size(); ++i) + { + auto& command = m_DrawList->CmdBuffer[i]; + command.ClipRect.x = command.ClipRect.x + m_ViewTransformPosition.x; + command.ClipRect.y = command.ClipRect.y + m_ViewTransformPosition.y; + command.ClipRect.z = command.ClipRect.z + m_ViewTransformPosition.x; + command.ClipRect.w = command.ClipRect.w + m_ViewTransformPosition.y; + } + } + + // Remove sentinel draw command if present + if (m_DrawListCommadBufferSize > 0) + { + if (m_DrawList->CmdBuffer.size() > m_DrawListCommadBufferSize && m_DrawList->CmdBuffer[m_DrawListCommadBufferSize].UserCallback == ImDrawCallback_ImCanvas) + m_DrawList->CmdBuffer.erase(m_DrawList->CmdBuffer.Data + m_DrawListCommadBufferSize); + else if (m_DrawList->CmdBuffer.size() >= m_DrawListCommadBufferSize && m_DrawList->CmdBuffer[m_DrawListCommadBufferSize - 1].UserCallback == ImDrawCallback_ImCanvas) + m_DrawList->CmdBuffer.erase(m_DrawList->CmdBuffer.Data + m_DrawListCommadBufferSize - 1); + } + + auto& fringeScale = ImFringeScaleRef(m_DrawList); + fringeScale = m_LastFringeScale; + + // And pop \o/ + ImGui::PopClipRect(); + + RestoreInputState(); + RestoreViewportState(); +} diff --git a/cpp/vendor/imgui-node-editor/imgui_canvas.h b/cpp/vendor/imgui-node-editor/imgui_canvas.h new file mode 100644 index 00000000..de2a6f56 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_canvas.h @@ -0,0 +1,273 @@ +// Canvas widget - view over infinite virtual space. +// +// Canvas allows you to draw your widgets anywhere over infinite space and provide +// view over it with support for panning and scaling. +// +// When you enter a canvas ImGui is moved to virtual space which mean: +// - ImGui::GetCursorScreenPos() return (0, 0) and which correspond to top left corner +// of the canvas on the screen (this can be changed using CanvasView()). +// - Mouse input is brought to canvas space, so widgets works as usual. +// - Everything you draw with ImDrawList will be in virtual space. +// +// By default origin point is on top left corner of canvas widget. It can be +// changed with call to CanvasView() where you can specify what part of space +// should be viewed by setting viewport origin point and scale. Current state +// can be queried with CanvasViewOrigin() and CanvasViewScale(). +// +// Viewport size is controlled by 'size' parameter in BeginCanvas(). You can query +// it using CanvasContentMin/Max/Size functions. They are useful if you to not specify +// canvas size in which case all free space is used. +// +// Bounds of visible region of infinite space can be queried using CanvasViewMin/Max/Size +// functions. Everything that is drawn outside of this region will be clipped +// as usual in ImGui. +// +// While drawing inside canvas you can translate position from world (usual ImGui space) +// to virtual space and back using CanvasFromWorld()/CanvasToWorld(). +// +// Canvas can be nested in each other (they are regular widgets after all). There +// is a way to transform position between current and parent canvas with +// CanvasFromParent()/CanvasToParent(). +// +// Sometimes in more elaborate scenarios you want to move out canvas virtual space, +// do something and came back. You can do that with SuspendCanvas() and ResumeCanvas(). +// +// Note: +// It is not valid to call canvas API outside of BeginCanvas() / EndCanvas() scope. +// +// VERSION 0.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +# ifndef __IMGUI_EX_CANVAS_H__ +# define __IMGUI_EX_CANVAS_H__ +# pragma once + +# include +# include // ImRect, ImFloor + +#ifndef IMGUIEX_CANVAS_API +#define IMGUIEX_CANVAS_API +#endif + +namespace ImGuiEx { + +struct CanvasView +{ + ImVec2 Origin; + float Scale = 1.0f; + float InvScale = 1.0f; + + CanvasView() = default; + CanvasView(const ImVec2& origin, float scale) + : Origin(origin) + , Scale(scale) + , InvScale(scale ? 1.0f / scale : 0.0f) + { + } + + void Set(const ImVec2& origin, float scale) + { + *this = CanvasView(origin, scale); + } +}; + +// Canvas widget represent view over infinite plane. +// +// It acts like a child window without scroll bars with +// ability to zoom to specific part of canvas plane. +// +// Widgets are clipped according to current view exactly +// same way ImGui do. To avoid `missing widgets` artifacts first +// setup visible region with SetView() then draw content. +// +// Everything drawn with ImDrawList betwen calls to Begin()/End() +// will be drawn on canvas plane. This behavior can be suspended +// by calling Suspend() and resumed by calling Resume(). +// +// Warning: +// Please do not interleave canvas with use of channel splitter. +// Keep channel splitter contained inside canvas or always +// call canvas functions from same channel. +struct Canvas +{ + // Begins drawing content of canvas plane. + // + // When false is returned that mean canvas is not visible to the + // user can drawing should be skipped and End() not called. + // When true is returned drawing must be ended with call to End(). + // + // If any size component is equal to zero or less canvas will + // automatically expand to all available area on that axis. + // So (0, 300) will take horizontal space and have height + // of 300 points. (0, 0) will take all remaining space of + // the window. + // + // You can query size of the canvas while it is being drawn + // by calling Rect(). + IMGUIEX_CANVAS_API bool Begin(const char* id, const ImVec2& size); + IMGUIEX_CANVAS_API bool Begin(ImGuiID id, const ImVec2& size); + + // Ends interaction with canvas plane. + // + // Must be called only when Begin() retuned true. + IMGUIEX_CANVAS_API void End(); + + // Sets visible region of canvas plane. + // + // Origin is an offset of infinite plane origin from top left + // corner of the canvas. + // + // Scale greater than 1 make canvas content be bigger, less than 1 smaller. + IMGUIEX_CANVAS_API void SetView(const ImVec2& origin, float scale); + IMGUIEX_CANVAS_API void SetView(const CanvasView& view); + + // Centers view over specific point on canvas plane. + // + // View will be centered on specific point by changing origin + // but not scale. + IMGUIEX_CANVAS_API void CenterView(const ImVec2& canvasPoint); + + // Calculates view over specific point on canvas plane. + IMGUIEX_CANVAS_API CanvasView CalcCenterView(const ImVec2& canvasPoint) const; + + // Centers view over specific rectangle on canvas plane. + // + // Whole rectangle will fit in canvas view. This will affect both + // origin and scale. + IMGUIEX_CANVAS_API void CenterView(const ImRect& canvasRect); + + // Calculates view over specific rectangle on canvas plane. + IMGUIEX_CANVAS_API CanvasView CalcCenterView(const ImRect& canvasRect) const; + + // Suspends canvas by returning to normal ImGui transformation space. + // While suspended UI will not be drawn on canvas plane. + // + // Calls to Suspend()/Resume() are symetrical. Each call to Suspend() + // must be matched with call to Resume(). + IMGUIEX_CANVAS_API void Suspend(); + IMGUIEX_CANVAS_API void Resume(); + + // Transforms point from canvas plane to ImGui. + IMGUIEX_CANVAS_API ImVec2 FromLocal(const ImVec2& point) const; + IMGUIEX_CANVAS_API ImVec2 FromLocal(const ImVec2& point, const CanvasView& view) const; + + // Transforms vector from canvas plant to ImGui. + IMGUIEX_CANVAS_API ImVec2 FromLocalV(const ImVec2& vector) const; + IMGUIEX_CANVAS_API ImVec2 FromLocalV(const ImVec2& vector, const CanvasView& view) const; + + // Transforms point from ImGui to canvas plane. + IMGUIEX_CANVAS_API ImVec2 ToLocal(const ImVec2& point) const; + IMGUIEX_CANVAS_API ImVec2 ToLocal(const ImVec2& point, const CanvasView& view) const; + + // Transforms vector from ImGui to canvas plane. + IMGUIEX_CANVAS_API ImVec2 ToLocalV(const ImVec2& vector) const; + IMGUIEX_CANVAS_API ImVec2 ToLocalV(const ImVec2& vector, const CanvasView& view) const; + + // Returns widget bounds. + // + // Note: + // Rect is valid after call to Begin(). + const ImRect& Rect() const { return m_WidgetRect; } + + // Returns visible region on canvas plane (in canvas plane coordinates). + const ImRect& ViewRect() const { return m_ViewRect; } + + // Calculates visible region for view. + IMGUIEX_CANVAS_API ImRect CalcViewRect(const CanvasView& view) const; + + // Returns current view. + const CanvasView& View() const { return m_View; } + + // Returns origin of the view. + // + // Origin is an offset of infinite plane origin from top left + // corner of the canvas. + const ImVec2& ViewOrigin() const { return m_View.Origin; } + + // Returns scale of the view. + float ViewScale() const { return m_View.Scale; } + + // Returns true if canvas is suspended. + // + // See: Suspend()/Resume() + bool IsSuspended() const { return m_SuspendCounter > 0; } + +private: +# define IMGUI_EX_CANVAS_DEFERED() 0 + +# if IMGUI_EX_CANVAS_DEFERED() + struct Range + { + int BeginVertexIndex = 0; + int EndVertexIndex = 0; + int BeginComandIndex = 0; + int EndCommandIndex = 0; + }; +# endif + + void UpdateViewTransformPosition(); + + void SaveInputState(); + void RestoreInputState(); + + void SaveViewportState(); + void RestoreViewportState(); + + void EnterLocalSpace(); + void LeaveLocalSpace(); + + bool m_InBeginEnd = false; + + ImVec2 m_WidgetPosition; + ImVec2 m_WidgetSize; + ImRect m_WidgetRect; + + ImDrawList* m_DrawList = nullptr; + int m_ExpectedChannel = 0; + +# if IMGUI_EX_CANVAS_DEFERED() + ImVector m_Ranges; + Range* m_CurrentRange = nullptr; +# endif + + int m_DrawListFirstCommandIndex = 0; + int m_DrawListCommadBufferSize = 0; + int m_DrawListStartVertexIndex = 0; + + CanvasView m_View; + ImRect m_ViewRect; + + ImVec2 m_ViewTransformPosition; + + int m_SuspendCounter = 0; + + float m_LastFringeScale = 1.0f; + + ImVec2 m_MousePosBackup; + ImVec2 m_MousePosPrevBackup; + ImVec2 m_MouseClickedPosBackup[IM_ARRAYSIZE(ImGuiIO::MouseClickedPos)]; + ImVec2 m_WindowCursorMaxBackup; + +# if defined(IMGUI_HAS_VIEWPORT) + ImVec2 m_WindowPosBackup; + ImVec2 m_ViewportPosBackup; + ImVec2 m_ViewportSizeBackup; +# if IMGUI_VERSION_NUM > 18002 + ImVec2 m_ViewportWorkPosBackup; + ImVec2 m_ViewportWorkSizeBackup; +# else + ImVec2 m_ViewportWorkOffsetMinBackup; + ImVec2 m_ViewportWorkOffsetMaxBackup; +# endif +# endif +}; + +} // namespace ImGuiEx + +# endif // __IMGUI_EX_CANVAS_H__ \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/imgui_extra_math.h b/cpp/vendor/imgui-node-editor/imgui_extra_math.h new file mode 100644 index 00000000..3df615ac --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_extra_math.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_EXTRA_MATH_H__ +# define __IMGUI_EXTRA_MATH_H__ +# pragma once + + +//------------------------------------------------------------------------------ +# ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +# endif +# include +# include + + +//------------------------------------------------------------------------------ +struct ImLine +{ + ImVec2 A, B; +}; + + +//------------------------------------------------------------------------------ +# if IMGUI_VERSION_NUM < 19002 +inline bool operator==(const ImVec2& lhs, const ImVec2& rhs); +inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs); +# endif +inline ImVec2 operator*(const float lhs, const ImVec2& rhs); +# if IMGUI_VERSION_NUM < 18955 +inline ImVec2 operator-(const ImVec2& lhs); +# endif + + +//------------------------------------------------------------------------------ +inline float ImLength(float v); +inline float ImLength(const ImVec2& v); +inline float ImLengthSqr(float v); +inline ImVec2 ImNormalized(const ImVec2& v); + + +//------------------------------------------------------------------------------ +inline bool ImRect_IsEmpty(const ImRect& rect); +inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge); +inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge, float radius); +inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImRect& b); +inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b); +inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b, float radius_a, float radius_b); + + + +//------------------------------------------------------------------------------ +namespace ImEasing { + +template +inline V EaseOutQuad(V b, V c, T t) +{ + return b - c * (t * (t - 2)); +} + +} // namespace ImEasing + + +//------------------------------------------------------------------------------ +# include "imgui_extra_math.inl" + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_EXTRA_MATH_H__ diff --git a/cpp/vendor/imgui-node-editor/imgui_extra_math.inl b/cpp/vendor/imgui-node-editor/imgui_extra_math.inl new file mode 100644 index 00000000..7dc839f1 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_extra_math.inl @@ -0,0 +1,195 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_EXTRA_MATH_INL__ +# define __IMGUI_EXTRA_MATH_INL__ +# pragma once + + +//------------------------------------------------------------------------------ +# include "imgui_extra_math.h" + + +//------------------------------------------------------------------------------ +# if IMGUI_VERSION_NUM < 19002 +inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) +{ + return lhs.x == rhs.x && lhs.y == rhs.y; +} + +inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) +{ + return lhs.x != rhs.x || lhs.y != rhs.y; +} +# endif + +# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED +inline ImVec2 operator*(const float lhs, const ImVec2& rhs) +{ + return ImVec2(lhs * rhs.x, lhs * rhs.y); +} +# endif + +# if IMGUI_VERSION_NUM < 18955 +inline ImVec2 operator-(const ImVec2& lhs) +{ + return ImVec2(-lhs.x, -lhs.y); +} +# endif + + +//------------------------------------------------------------------------------ +inline float ImLength(float v) +{ + return v; +} + +inline float ImLength(const ImVec2& v) +{ + return ImSqrt(ImLengthSqr(v)); +} + +inline float ImLengthSqr(float v) +{ + return v * v; +} + +inline ImVec2 ImNormalized(const ImVec2& v) +{ + return v * ImInvLength(v, 0.0f); +} + + + + +//------------------------------------------------------------------------------ +inline bool ImRect_IsEmpty(const ImRect& rect) +{ + return rect.Min.x >= rect.Max.x + || rect.Min.y >= rect.Max.y; +} + +inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge) +{ + if (!snap_to_edge && rect.Contains(p)) + return p; + + return ImVec2( + (p.x > rect.Max.x) ? rect.Max.x : (p.x < rect.Min.x ? rect.Min.x : p.x), + (p.y > rect.Max.y) ? rect.Max.y : (p.y < rect.Min.y ? rect.Min.y : p.y) + ); +} + +inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge, float radius) +{ + auto point = ImRect_ClosestPoint(rect, p, snap_to_edge); + + const auto offset = p - point; + const auto distance_sq = offset.x * offset.x + offset.y * offset.y; + if (distance_sq <= 0) + return point; + + const auto distance = ImSqrt(distance_sq); + + return point + offset * (ImMin(distance, radius) * (1.0f / distance)); +} + +inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImRect& other) +{ + ImVec2 result; + if (other.Min.x >= rect.Max.x) + result.x = rect.Max.x; + else if (other.Max.x <= rect.Min.x) + result.x = rect.Min.x; + else + result.x = (ImMax(rect.Min.x, other.Min.x) + ImMin(rect.Max.x, other.Max.x)) / 2; + + if (other.Min.y >= rect.Max.y) + result.y = rect.Max.y; + else if (other.Max.y <= rect.Min.y) + result.y = rect.Min.y; + else + result.y = (ImMax(rect.Min.y, other.Min.y) + ImMin(rect.Max.y, other.Max.y)) / 2; + + return result; +} + +inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b) +{ + ImLine result; + result.A = ImRect_ClosestPoint(rect_a, rect_b); + result.B = ImRect_ClosestPoint(rect_b, rect_a); + + auto distribute = [](float& a, float& b, float a0, float a1, float b0, float b1) + { + if (a0 >= b1 || a1 <= b0) + return; + + const auto aw = a1 - a0; + const auto bw = b1 - b0; + + if (aw > bw) + { + b = b0 + bw - bw * (a - a0) / aw; + a = b; + } + else if (aw < bw) + { + a = a0 + aw - aw * (b - b0) / bw; + b = a; + } + }; + + distribute(result.A.x, result.B.x, rect_a.Min.x, rect_a.Max.x, rect_b.Min.x, rect_b.Max.x); + distribute(result.A.y, result.B.y, rect_a.Min.y, rect_a.Max.y, rect_b.Min.y, rect_b.Max.y); + + return result; +} + +inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b, float radius_a, float radius_b) +{ + auto line = ImRect_ClosestLine(rect_a, rect_b); + if (radius_a < 0) + radius_a = 0; + if (radius_b < 0) + radius_b = 0; + + if (radius_a == 0 && radius_b == 0) + return line; + + const auto offset = line.B - line.A; + const auto length_sq = offset.x * offset.x + offset.y * offset.y; + const auto radius_a_sq = radius_a * radius_a; + const auto radius_b_sq = radius_b * radius_b; + + if (length_sq <= 0) + return line; + + const auto length = ImSqrt(length_sq); + const auto direction = ImVec2(offset.x / length, offset.y / length); + + const auto total_radius_sq = radius_a_sq + radius_b_sq; + if (total_radius_sq > length_sq) + { + const auto scale = length / (radius_a + radius_b); + radius_a *= scale; + radius_b *= scale; + } + + line.A = line.A + (direction * radius_a); + line.B = line.B - (direction * radius_b); + + return line; +} + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_EXTRA_MATH_INL__ diff --git a/cpp/vendor/imgui-node-editor/imgui_node_editor.cpp b/cpp/vendor/imgui-node-editor/imgui_node_editor.cpp new file mode 100644 index 00000000..3ca9eb33 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_node_editor.cpp @@ -0,0 +1,5874 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# include "imgui_node_editor_internal.h" +# include // snprintf +# include +# include +# include +# include +# include +# include +# include +# include + +// https://stackoverflow.com/a/8597498 +# define DECLARE_HAS_NESTED(Name, Member) \ + \ + template \ + struct has_nested_ ## Name \ + { \ + typedef char yes; \ + typedef yes(&no)[2]; \ + \ + template static yes test(decltype(U::Member)*); \ + template static no test(...); \ + \ + static bool const value = sizeof(test(0)) == sizeof(yes); \ + }; + +// LOCAL NAMESPACE TO DEFINE FLOOR FUNCTION, deprecated in imgui +namespace { + void FloorRect(ImRect & rect) + { + rect.Min.x = IM_TRUNC(rect.Min.x); + rect.Min.y = IM_TRUNC(rect.Min.y); + rect.Max.x = IM_TRUNC(rect.Max.x); + rect.Max.y = IM_TRUNC(rect.Max.y); + } +} +namespace ImGui { + ImGuiKey GetKeyIndex(ImGuiKey key) + { + IM_ASSERT(IsNamedKey(key)); + return key; // already the correct 'index' in modern ImGui + } +} + + + +namespace ax { +namespace NodeEditor { +namespace Detail { + +# if !defined(IMGUI_VERSION_NUM) || (IMGUI_VERSION_NUM < 18822) +# define DECLARE_KEY_TESTER(Key) \ + DECLARE_HAS_NESTED(Key, Key) \ + struct KeyTester_ ## Key \ + { \ + template \ + static int Get(typename std::enable_if::value, T>::type*) \ + { \ + return ImGui::GetKeyIndex(T::Key); \ + } \ + \ + template \ + static int Get(typename std::enable_if::value, T>::type*) \ + { \ + return -1; \ + } \ + } + +DECLARE_KEY_TESTER(ImGuiKey_F); +DECLARE_KEY_TESTER(ImGuiKey_D); + +static inline int GetKeyIndexForF() +{ + return KeyTester_ImGuiKey_F::Get(nullptr); +} + +static inline int GetKeyIndexForD() +{ + return KeyTester_ImGuiKey_D::Get(nullptr); +} +# else +static inline ImGuiKey GetKeyIndexForF() +{ + return ImGuiKey_F; +} + +static inline ImGuiKey GetKeyIndexForD() +{ + return ImGuiKey_D; +} +# endif + +} // namespace Detail +} // namespace NodeEditor +} // namespace ax + + +//------------------------------------------------------------------------------ +namespace ed = ax::NodeEditor::Detail; + + +//------------------------------------------------------------------------------ +static const int c_BackgroundChannelCount = 1; +static const int c_LinkChannelCount = 4; +static const int c_UserLayersCount = 5; + +static const int c_UserLayerChannelStart = 0; +static const int c_BackgroundChannelStart = c_UserLayerChannelStart + c_UserLayersCount; +static const int c_LinkStartChannel = c_BackgroundChannelStart + c_BackgroundChannelCount; +static const int c_NodeStartChannel = c_LinkStartChannel + c_LinkChannelCount; + +static const int c_BackgroundChannel_SelectionRect = c_BackgroundChannelStart + 0; + +static const int c_UserChannel_Content = c_UserLayerChannelStart + 1; +static const int c_UserChannel_Grid = c_UserLayerChannelStart + 2; +static const int c_UserChannel_HintsBackground = c_UserLayerChannelStart + 3; +static const int c_UserChannel_Hints = c_UserLayerChannelStart + 4; + +static const int c_LinkChannel_Selection = c_LinkStartChannel + 0; +static const int c_LinkChannel_Links = c_LinkStartChannel + 1; +static const int c_LinkChannel_Flow = c_LinkStartChannel + 2; +static const int c_LinkChannel_NewLink = c_LinkStartChannel + 3; + +static const int c_ChannelsPerNode = 5; +static const int c_NodeBaseChannel = 0; +static const int c_NodeBackgroundChannel = 1; +static const int c_NodeUserBackgroundChannel = 2; +static const int c_NodePinChannel = 3; +static const int c_NodeContentChannel = 4; + +static const float c_GroupSelectThickness = 6.0f; // canvas pixels +static const float c_LinkSelectThickness = 5.0f; // canvas pixels +static const float c_NavigationZoomMargin = 0.1f; // percentage of visible bounds +static const float c_MouseZoomDuration = 0.15f; // seconds +static const float c_SelectionFadeOutDuration = 0.15f; // seconds + +static const auto c_MaxMoveOverEdgeSpeed = 10.0f; +static const auto c_MaxMoveOverEdgeDistance = 300.0f; + +#if IMGUI_VERSION_NUM > 18101 +static const auto c_AllRoundCornersFlags = ImDrawFlags_RoundCornersAll; +#else +static const auto c_AllRoundCornersFlags = 15; +#endif + + +//------------------------------------------------------------------------------ +# if defined(_DEBUG) && defined(_WIN32) +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* string); + +static void LogV(const char* fmt, va_list args) +{ + const int buffer_size = 1024; + static char buffer[1024]; + + vsnprintf(buffer, buffer_size - 1, fmt, args); + buffer[buffer_size - 1] = 0; + + ImGui::LogText("\nNode Editor: %s", buffer); + + OutputDebugStringA("NodeEditor: "); + OutputDebugStringA(buffer); + OutputDebugStringA("\n"); +} +# endif + +void ed::Log(const char* fmt, ...) +{ +# if defined(_DEBUG) && defined(_WIN32) + va_list args; + va_start(args, fmt); + LogV(fmt, args); + va_end(args); +# endif +} + + +//------------------------------------------------------------------------------ +static bool IsGroup(const ed::Node* node) +{ + if (node && node->m_Type == ed::NodeType::Group) + return true; + else + return false; +} + + +//------------------------------------------------------------------------------ +static void ImDrawListSplitter_Grow(ImDrawList* draw_list, ImDrawListSplitter* splitter, int channels_count) +{ + IM_ASSERT(splitter != nullptr); + IM_ASSERT(splitter->_Count <= channels_count); + + if (splitter->_Count == 1) + { + splitter->Split(draw_list, channels_count); + return; + } + + int old_channels_count = splitter->_Channels.Size; + if (old_channels_count < channels_count) + { + splitter->_Channels.reserve(channels_count); + splitter->_Channels.resize(channels_count); + } + int old_used_channels_count = splitter->_Count; + splitter->_Count = channels_count; + + for (int i = old_used_channels_count; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&splitter->_Channels[i]) ImDrawChannel(); + } + else + { + splitter->_Channels[i]._CmdBuffer.resize(0); + splitter->_Channels[i]._IdxBuffer.resize(0); + } + } +} + +static void ImDrawList_ChannelsGrow(ImDrawList* draw_list, int channels_count) +{ + ImDrawListSplitter_Grow(draw_list, &draw_list->_Splitter, channels_count); +} + +static void ImDrawListSplitter_SwapChannels(ImDrawListSplitter* splitter, int left, int right) +{ + IM_ASSERT(left < splitter->_Count && right < splitter->_Count); + if (left == right) + return; + + auto currentChannel = splitter->_Current; + + auto* leftCmdBuffer = &splitter->_Channels[left]._CmdBuffer; + auto* leftIdxBuffer = &splitter->_Channels[left]._IdxBuffer; + auto* rightCmdBuffer = &splitter->_Channels[right]._CmdBuffer; + auto* rightIdxBuffer = &splitter->_Channels[right]._IdxBuffer; + + leftCmdBuffer->swap(*rightCmdBuffer); + leftIdxBuffer->swap(*rightIdxBuffer); + + if (currentChannel == left) + splitter->_Current = right; + else if (currentChannel == right) + splitter->_Current = left; +} + +static void ImDrawList_SwapChannels(ImDrawList* drawList, int left, int right) +{ + ImDrawListSplitter_SwapChannels(&drawList->_Splitter, left, right); +} + +static void ImDrawList_SwapSplitter(ImDrawList* drawList, ImDrawListSplitter& splitter) +{ + auto& currentSplitter = drawList->_Splitter; + + std::swap(currentSplitter._Current, splitter._Current); + std::swap(currentSplitter._Count, splitter._Count); + currentSplitter._Channels.swap(splitter._Channels); +} + +//static void ImDrawList_TransformChannel_Inner(ImVector& vtxBuffer, const ImVector& idxBuffer, const ImVector& cmdBuffer, const ImVec2& preOffset, const ImVec2& scale, const ImVec2& postOffset) +//{ +// auto idxRead = idxBuffer.Data; +// +// int indexOffset = 0; +// for (auto& cmd : cmdBuffer) +// { +// auto idxCount = cmd.ElemCount; +// +// if (idxCount == 0) continue; +// +// auto minIndex = idxRead[indexOffset]; +// auto maxIndex = idxRead[indexOffset]; +// +// for (auto i = 1u; i < idxCount; ++i) +// { +// auto idx = idxRead[indexOffset + i]; +// minIndex = std::min(minIndex, idx); +// maxIndex = ImMax(maxIndex, idx); +// } +// +// for (auto vtx = vtxBuffer.Data + minIndex, vtxEnd = vtxBuffer.Data + maxIndex + 1; vtx < vtxEnd; ++vtx) +// { +// vtx->pos.x = (vtx->pos.x + preOffset.x) * scale.x + postOffset.x; +// vtx->pos.y = (vtx->pos.y + preOffset.y) * scale.y + postOffset.y; +// } +// +// indexOffset += idxCount; +// } +//} + +//static void ImDrawList_TransformChannels(ImDrawList* drawList, int begin, int end, const ImVec2& preOffset, const ImVec2& scale, const ImVec2& postOffset) +//{ +// int lastCurrentChannel = drawList->_ChannelsCurrent; +// if (lastCurrentChannel != 0) +// drawList->ChannelsSetCurrent(0); +// +// auto& vtxBuffer = drawList->VtxBuffer; +// +// if (begin == 0 && begin != end) +// { +// ImDrawList_TransformChannel_Inner(vtxBuffer, drawList->IdxBuffer, drawList->CmdBuffer, preOffset, scale, postOffset); +// ++begin; +// } +// +// for (int channelIndex = begin; channelIndex < end; ++channelIndex) +// { +// auto& channel = drawList->_Channels[channelIndex]; +// ImDrawList_TransformChannel_Inner(vtxBuffer, channel.IdxBuffer, channel.CmdBuffer, preOffset, scale, postOffset); +// } +// +// if (lastCurrentChannel != 0) +// drawList->ChannelsSetCurrent(lastCurrentChannel); +//} + +//static void ImDrawList_ClampClipRects_Inner(ImVector& cmdBuffer, const ImVec4& clipRect, const ImVec2& offset) +//{ +// for (auto& cmd : cmdBuffer) +// { +// cmd.ClipRect.x = ImMax(cmd.ClipRect.x + offset.x, clipRect.x); +// cmd.ClipRect.y = ImMax(cmd.ClipRect.y + offset.y, clipRect.y); +// cmd.ClipRect.z = std::min(cmd.ClipRect.z + offset.x, clipRect.z); +// cmd.ClipRect.w = std::min(cmd.ClipRect.w + offset.y, clipRect.w); +// } +//} + +//static void ImDrawList_TranslateAndClampClipRects(ImDrawList* drawList, int begin, int end, const ImVec2& offset) +//{ +// int lastCurrentChannel = drawList->_ChannelsCurrent; +// if (lastCurrentChannel != 0) +// drawList->ChannelsSetCurrent(0); +// +// auto clipRect = drawList->_ClipRectStack.back(); +// +// if (begin == 0 && begin != end) +// { +// ImDrawList_ClampClipRects_Inner(drawList->CmdBuffer, clipRect, offset); +// ++begin; +// } +// +// for (int channelIndex = begin; channelIndex < end; ++channelIndex) +// { +// auto& channel = drawList->_Channels[channelIndex]; +// ImDrawList_ClampClipRects_Inner(channel.CmdBuffer, clipRect, offset); +// } +// +// if (lastCurrentChannel != 0) +// drawList->ChannelsSetCurrent(lastCurrentChannel); +//} + +static void ImDrawList_PathBezierOffset(ImDrawList* drawList, float offset, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3) +{ + using namespace ed; + + auto acceptPoint = [drawList, offset](const ImCubicBezierSubdivideSample& r) + { + drawList->PathLineTo(r.Point + ImNormalized(ImVec2(-r.Tangent.y, r.Tangent.x)) * offset); + }; + + ImCubicBezierSubdivide(acceptPoint, p0, p1, p2, p3); +} + +/* +static void ImDrawList_PolyFillScanFlood(ImDrawList *draw, std::vector* poly, ImColor color, int gap = 1, float strokeWidth = 1.0f) +{ + std::vector scanHits; + ImVec2 min, max; // polygon min/max points + auto io = ImGui::GetIO(); + float y; + bool isMinMaxDone = false; + unsigned int polysize = poly->size(); + + // find the orthagonal bounding box + // probably can put this as a predefined + if (!isMinMaxDone) + { + min.x = min.y = FLT_MAX; + max.x = max.y = FLT_MIN; + for (auto p : *poly) + { + if (p.x < min.x) min.x = p.x; + if (p.y < min.y) min.y = p.y; + if (p.x > max.x) max.x = p.x; + if (p.y > max.y) max.y = p.y; + } + isMinMaxDone = true; + } + + // Bounds check + if ((max.x < 0) || (min.x > io.DisplaySize.x) || (max.y < 0) || (min.y > io.DisplaySize.y)) return; + + // Vertically clip + if (min.y < 0) min.y = 0; + if (max.y > io.DisplaySize.y) max.y = io.DisplaySize.y; + + // so we know we start on the outside of the object we step out by 1. + min.x -= 1; + max.x += 1; + + // Initialise our starting conditions + y = min.y; + + // Go through each scan line iteratively, jumping by 'gap' pixels each time + while (y < max.y) + { + scanHits.clear(); + + { + int jump = 1; + ImVec2 fp = poly->at(0); + + for (size_t i = 0; i < polysize - 1; i++) + { + ImVec2 pa = poly->at(i); + ImVec2 pb = poly->at(i + 1); + + // jump double/dud points + if (pa.x == pb.x && pa.y == pb.y) continue; + + // if we encounter our hull/poly start point, then we've now created the + // closed + // hull, jump the next segment and reset the first-point + if ((!jump) && (fp.x == pb.x) && (fp.y == pb.y)) + { + if (i < polysize - 2) + { + fp = poly->at(i + 2); + jump = 1; + i++; + } + } + else + { + jump = 0; + } + + // test to see if this segment makes the scan-cut. + if ((pa.y > pb.y && y < pa.y && y > pb.y) || (pa.y < pb.y && y > pa.y && y < pb.y)) + { + ImVec2 intersect; + + intersect.y = y; + if (pa.x == pb.x) + { + intersect.x = pa.x; + } + else + { + intersect.x = (pb.x - pa.x) / (pb.y - pa.y) * (y - pa.y) + pa.x; + } + scanHits.push_back(intersect); + } + } + + // Sort the scan hits by X, so we have a proper left->right ordering + sort(scanHits.begin(), scanHits.end(), [](ImVec2 const &a, ImVec2 const &b) { return a.x < b.x; }); + + // generate the line segments. + { + int i = 0; + int l = scanHits.size() - 1; // we need pairs of points, this prevents segfault. + for (i = 0; i < l; i += 2) + { + draw->AddLine(scanHits[i], scanHits[i + 1], color, strokeWidth); + } + } + } + y += gap; + } // for each scan line + scanHits.clear(); +} +*/ + +static void ImDrawList_AddBezierWithArrows(ImDrawList* drawList, const ImCubicBezierPoints& curve, float thickness, + float startArrowSize, float startArrowWidth, float endArrowSize, float endArrowWidth, + bool fill, ImU32 color, float strokeThickness, const ImVec2* startDirHint = nullptr, const ImVec2* endDirHint = nullptr) +{ + using namespace ax; + + if ((color >> 24) == 0) + return; + + const auto half_thickness = thickness * 0.5f; + + if (fill) + { + drawList->AddBezierCubic(curve.P0, curve.P1, curve.P2, curve.P3, color, thickness); + + if (startArrowSize > 0.0f) + { + const auto start_dir = ImNormalized(startDirHint ? *startDirHint : ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, 0.0f)); + const auto start_n = ImVec2(-start_dir.y, start_dir.x); + const auto half_width = startArrowWidth * 0.5f; + const auto tip = curve.P0 - start_dir * startArrowSize; + + drawList->PathLineTo(curve.P0 - start_n * ImMax(half_width, half_thickness)); + drawList->PathLineTo(curve.P0 + start_n * ImMax(half_width, half_thickness)); + drawList->PathLineTo(tip); + drawList->PathFillConvex(color); + } + + if (endArrowSize > 0.0f) + { + const auto end_dir = ImNormalized(endDirHint ? -*endDirHint : ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, 1.0f)); + const auto end_n = ImVec2( -end_dir.y, end_dir.x); + const auto half_width = endArrowWidth * 0.5f; + const auto tip = curve.P3 + end_dir * endArrowSize; + + drawList->PathLineTo(curve.P3 + end_n * ImMax(half_width, half_thickness)); + drawList->PathLineTo(curve.P3 - end_n * ImMax(half_width, half_thickness)); + drawList->PathLineTo(tip); + drawList->PathFillConvex(color); + } + } + else + { + if (startArrowSize > 0.0f) + { + const auto start_dir = ImNormalized(ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, 0.0f)); + const auto start_n = ImVec2(-start_dir.y, start_dir.x); + const auto half_width = startArrowWidth * 0.5f; + const auto tip = curve.P0 - start_dir * startArrowSize; + + if (half_width > half_thickness) + drawList->PathLineTo(curve.P0 - start_n * half_width); + drawList->PathLineTo(tip); + if (half_width > half_thickness) + drawList->PathLineTo(curve.P0 + start_n * half_width); + } + + ImDrawList_PathBezierOffset(drawList, half_thickness, curve.P0, curve.P1, curve.P2, curve.P3); + + if (endArrowSize > 0.0f) + { + const auto end_dir = ImNormalized(ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, 1.0f)); + const auto end_n = ImVec2( -end_dir.y, end_dir.x); + const auto half_width = endArrowWidth * 0.5f; + const auto tip = curve.P3 + end_dir * endArrowSize; + + if (half_width > half_thickness) + drawList->PathLineTo(curve.P3 + end_n * half_width); + drawList->PathLineTo(tip); + if (half_width > half_thickness) + drawList->PathLineTo(curve.P3 - end_n * half_width); + } + + ImDrawList_PathBezierOffset(drawList, half_thickness, curve.P3, curve.P2, curve.P1, curve.P0); + + drawList->PathStroke(color, true, strokeThickness); + } +} + + + + +//------------------------------------------------------------------------------ +// +// Pin +// +//------------------------------------------------------------------------------ +void ed::Pin::Draw(ImDrawList* drawList, DrawFlags flags) +{ + if (flags & Hovered) + { + drawList->ChannelsSetCurrent(m_Node->m_Channel + c_NodePinChannel); + + drawList->AddRectFilled(m_Bounds.Min, m_Bounds.Max, + m_Color, m_Rounding, m_Corners); + + if (m_BorderWidth > 0.0f) + { + FringeScaleScope fringe(1.0f); + drawList->AddRect(m_Bounds.Min, m_Bounds.Max, + m_BorderColor, m_Rounding, m_Corners, m_BorderWidth); + } + + if (!Editor->IsSelected(m_Node)) + m_Node->Draw(drawList, flags); + } +} + +ImVec2 ed::Pin::GetClosestPoint(const ImVec2& p) const +{ + auto pivot = m_Pivot; + auto extent = m_Radius + m_ArrowSize; + + if (m_SnapLinkToDir && extent > 0.0f) + { + pivot.Min += m_Dir * extent; + pivot.Max += m_Dir * extent; + + extent = 0; + } + + return ImRect_ClosestPoint(pivot, p, true, extent); +} + +ImLine ed::Pin::GetClosestLine(const Pin* pin) const +{ + auto pivotA = m_Pivot; + auto pivotB = pin->m_Pivot; + auto extentA = m_Radius + m_ArrowSize; + auto extentB = pin->m_Radius + pin->m_ArrowSize; + + if (m_SnapLinkToDir && extentA > 0.0f) + { + pivotA.Min += m_Dir * extentA; + pivotA.Max += m_Dir * extentA; + + extentA = 0; + } + + if (pin->m_SnapLinkToDir && extentB > 0.0f) + { + pivotB.Min += pin->m_Dir * extentB; + pivotB.Max += pin->m_Dir * extentB; + + extentB = 0; + } + + return ImRect_ClosestLine(pivotA, pivotB, extentA, extentB); +} + + + + +//------------------------------------------------------------------------------ +// +// Node +// +//------------------------------------------------------------------------------ +bool ed::Node::AcceptDrag() +{ + m_DragStart = m_Bounds.Min; + return true; +} + +void ed::Node::UpdateDrag(const ImVec2& offset) +{ + auto size = m_Bounds.GetSize(); + m_Bounds.Min = ImFloor(m_DragStart + offset); + m_Bounds.Max = m_Bounds.Min + size; +} + +bool ed::Node::EndDrag() +{ + return m_Bounds.Min != m_DragStart; +} + +void ed::Node::Draw(ImDrawList* drawList, DrawFlags flags) +{ + if (flags == Detail::Object::None) + { + drawList->ChannelsSetCurrent(m_Channel + c_NodeBackgroundChannel); + + drawList->AddRectFilled( + m_Bounds.Min, + m_Bounds.Max, + m_Color, m_Rounding); + + if (IsGroup(this)) + { + drawList->AddRectFilled( + m_GroupBounds.Min, + m_GroupBounds.Max, + m_GroupColor, m_GroupRounding); + + if (m_GroupBorderWidth > 0.0f) + { + FringeScaleScope fringe(1.0f); + + drawList->AddRect( + m_GroupBounds.Min, + m_GroupBounds.Max, + m_GroupBorderColor, m_GroupRounding, c_AllRoundCornersFlags, m_GroupBorderWidth); + } + } + +# if 0 + // #debug: highlight group regions + auto drawRect = [drawList](const ImRect& rect, ImU32 color) + { + if (ImRect_IsEmpty(rect)) return; + drawList->AddRectFilled(rect.Min, rect.Max, color); + }; + + drawRect(GetRegionBounds(NodeRegion::Top), IM_COL32(255, 0, 0, 64)); + drawRect(GetRegionBounds(NodeRegion::Bottom), IM_COL32(255, 0, 0, 64)); + drawRect(GetRegionBounds(NodeRegion::Left), IM_COL32(0, 255, 0, 64)); + drawRect(GetRegionBounds(NodeRegion::Right), IM_COL32(0, 255, 0, 64)); + drawRect(GetRegionBounds(NodeRegion::TopLeft), IM_COL32(255, 0, 255, 64)); + drawRect(GetRegionBounds(NodeRegion::TopRight), IM_COL32(255, 0, 255, 64)); + drawRect(GetRegionBounds(NodeRegion::BottomLeft), IM_COL32(255, 0, 255, 64)); + drawRect(GetRegionBounds(NodeRegion::BottomRight), IM_COL32(255, 0, 255, 64)); + drawRect(GetRegionBounds(NodeRegion::Center), IM_COL32(0, 0, 255, 64)); + drawRect(GetRegionBounds(NodeRegion::Header), IM_COL32(0, 255, 255, 64)); +# endif + + DrawBorder(drawList, m_BorderColor, m_BorderWidth); + } + else if (flags & Selected) + { + const auto borderColor = Editor->GetColor(StyleColor_SelNodeBorder); + const auto& editorStyle = Editor->GetStyle(); + + drawList->ChannelsSetCurrent(m_Channel + c_NodeBaseChannel); + + DrawBorder(drawList, borderColor, editorStyle.SelectedNodeBorderWidth, editorStyle.SelectedNodeBorderOffset); + } + else if (!IsGroup(this) && (flags & Hovered)) + { + const auto borderColor = Editor->GetColor(StyleColor_HovNodeBorder); + const auto& editorStyle = Editor->GetStyle(); + + drawList->ChannelsSetCurrent(m_Channel + c_NodeBaseChannel); + + DrawBorder(drawList, borderColor, editorStyle.HoveredNodeBorderWidth, editorStyle.HoverNodeBorderOffset); + } +} + +void ed::Node::DrawBorder(ImDrawList* drawList, ImU32 color, float thickness, float offset) +{ + if (thickness > 0.0f) + { + const ImVec2 extraOffset = ImVec2(offset, offset); + + drawList->AddRect(m_Bounds.Min - extraOffset, m_Bounds.Max + extraOffset, + color, ImMax(0.0f, m_Rounding + offset), c_AllRoundCornersFlags, thickness); + } +} + +void ed::Node::GetGroupedNodes(std::vector& result, bool append) +{ + if (!append) + result.resize(0); + + if (!IsGroup(this)) + return; + + const auto firstNodeIndex = result.size(); + Editor->FindNodesInRect(m_GroupBounds, result, true, false); + + for (auto index = firstNodeIndex; index < result.size(); ++index) + result[index]->GetGroupedNodes(result, true); +} + +ImRect ed::Node::GetRegionBounds(NodeRegion region) const +{ + if (m_Type == NodeType::Node) + { + if (region == NodeRegion::Header) + return m_Bounds; + } + else if (m_Type == NodeType::Group) + { + const float activeAreaMinimumSize = ImMax(ImMax( + Editor->GetView().InvScale * c_GroupSelectThickness, + m_GroupBorderWidth), c_GroupSelectThickness); + const float minimumSize = activeAreaMinimumSize * 5; + + auto bounds = m_Bounds; + if (bounds.GetWidth() < minimumSize) + bounds.Expand(ImVec2(minimumSize - bounds.GetWidth(), 0.0f)); + if (bounds.GetHeight() < minimumSize) + bounds.Expand(ImVec2(0.0f, minimumSize - bounds.GetHeight())); + + if (region == NodeRegion::Top) + { + bounds.Max.y = bounds.Min.y + activeAreaMinimumSize; + bounds.Min.x += activeAreaMinimumSize; + bounds.Max.x -= activeAreaMinimumSize; + return bounds; + } + else if (region == NodeRegion::Bottom) + { + bounds.Min.y = bounds.Max.y - activeAreaMinimumSize; + bounds.Min.x += activeAreaMinimumSize; + bounds.Max.x -= activeAreaMinimumSize; + return bounds; + } + else if (region == NodeRegion::Left) + { + bounds.Max.x = bounds.Min.x + activeAreaMinimumSize; + bounds.Min.y += activeAreaMinimumSize; + bounds.Max.y -= activeAreaMinimumSize; + return bounds; + } + else if (region == NodeRegion::Right) + { + bounds.Min.x = bounds.Max.x - activeAreaMinimumSize; + bounds.Min.y += activeAreaMinimumSize; + bounds.Max.y -= activeAreaMinimumSize; + return bounds; + } + else if (region == NodeRegion::TopLeft) + { + bounds.Max.x = bounds.Min.x + activeAreaMinimumSize * 2; + bounds.Max.y = bounds.Min.y + activeAreaMinimumSize * 2; + return bounds; + } + else if (region == NodeRegion::TopRight) + { + bounds.Min.x = bounds.Max.x - activeAreaMinimumSize * 2; + bounds.Max.y = bounds.Min.y + activeAreaMinimumSize * 2; + return bounds; + } + else if (region == NodeRegion::BottomRight) + { + bounds.Min.x = bounds.Max.x - activeAreaMinimumSize * 2; + bounds.Min.y = bounds.Max.y - activeAreaMinimumSize * 2; + return bounds; + } + else if (region == NodeRegion::BottomLeft) + { + bounds.Max.x = bounds.Min.x + activeAreaMinimumSize * 2; + bounds.Min.y = bounds.Max.y - activeAreaMinimumSize * 2; + return bounds; + } + else if (region == NodeRegion::Header) + { + bounds.Min.x += activeAreaMinimumSize; + bounds.Max.x -= activeAreaMinimumSize; + bounds.Min.y += activeAreaMinimumSize; + bounds.Max.y = ImMax(bounds.Min.y + activeAreaMinimumSize, m_GroupBounds.Min.y); + return bounds; + } + else if (region == NodeRegion::Center) + { + bounds.Max.x -= activeAreaMinimumSize; + bounds.Min.y = ImMax(bounds.Min.y + activeAreaMinimumSize, m_GroupBounds.Min.y); + bounds.Min.x += activeAreaMinimumSize; + bounds.Max.y -= activeAreaMinimumSize; + return bounds; + } + } + + return ImRect(); +} + +ed::NodeRegion ed::Node::GetRegion(const ImVec2& point) const +{ + if (m_Type == NodeType::Node) + { + if (m_Bounds.Contains(point)) + return NodeRegion::Header; + else + return NodeRegion::None; + } + else if (m_Type == NodeType::Group) + { + static const NodeRegion c_Regions[] = + { + // Corners first, they may overlap other regions. + NodeRegion::TopLeft, + NodeRegion::TopRight, + NodeRegion::BottomLeft, + NodeRegion::BottomRight, + NodeRegion::Header, + NodeRegion::Top, + NodeRegion::Bottom, + NodeRegion::Left, + NodeRegion::Right, + NodeRegion::Center + }; + + for (auto region : c_Regions) + { + auto bounds = GetRegionBounds(region); + if (bounds.Contains(point)) + return region; + } + } + + return NodeRegion::None; +} + + + + +//------------------------------------------------------------------------------ +// +// Link +// +//------------------------------------------------------------------------------ +void ed::Link::Draw(ImDrawList* drawList, DrawFlags flags) +{ + if (flags == None) + { + drawList->ChannelsSetCurrent(c_LinkChannel_Links); + + Draw(drawList, m_Color, 0.0f); + } + else if (flags & Selected) + { + const auto borderColor = Editor->GetColor(StyleColor_SelLinkBorder); + + drawList->ChannelsSetCurrent(c_LinkChannel_Selection); + + Draw(drawList, borderColor, 4.5f); + } + else if (flags & Hovered) + { + const auto borderColor = Editor->GetColor(StyleColor_HovLinkBorder); + + drawList->ChannelsSetCurrent(c_LinkChannel_Selection); + + Draw(drawList, borderColor, 2.0f); + } + else if (flags & Highlighted) + { + drawList->ChannelsSetCurrent(c_LinkChannel_Selection); + + Draw(drawList, m_HighlightColor, 3.5f); + } +} + +void ed::Link::Draw(ImDrawList* drawList, ImU32 color, float extraThickness) const +{ + if (!m_IsLive) + return; + + const auto curve = GetCurve(); + + ImDrawList_AddBezierWithArrows(drawList, curve, m_Thickness + extraThickness, + m_StartPin && m_StartPin->m_ArrowSize > 0.0f ? m_StartPin->m_ArrowSize + extraThickness : 0.0f, + m_StartPin && m_StartPin->m_ArrowWidth > 0.0f ? m_StartPin->m_ArrowWidth + extraThickness : 0.0f, + m_EndPin && m_EndPin->m_ArrowSize > 0.0f ? m_EndPin->m_ArrowSize + extraThickness : 0.0f, + m_EndPin && m_EndPin->m_ArrowWidth > 0.0f ? m_EndPin->m_ArrowWidth + extraThickness : 0.0f, + true, color, 1.0f, + m_StartPin && m_StartPin->m_SnapLinkToDir ? &m_StartPin->m_Dir : nullptr, + m_EndPin && m_EndPin->m_SnapLinkToDir ? &m_EndPin->m_Dir : nullptr); +} + +void ed::Link::UpdateEndpoints() +{ + const auto line = m_StartPin->GetClosestLine(m_EndPin); + m_Start = line.A; + m_End = line.B; +} + +ImCubicBezierPoints ed::Link::GetCurve() const +{ + auto easeLinkStrength = [](const ImVec2& a, const ImVec2& b, float strength) + { + const auto distanceX = b.x - a.x; + const auto distanceY = b.y - a.y; + const auto distance = ImSqrt(distanceX * distanceX + distanceY * distanceY); + const auto halfDistance = distance * 0.5f; + + if (halfDistance < strength) + strength = strength * ImSin(IM_PI * 0.5f * halfDistance / strength); + + return strength; + }; + + const auto startStrength = easeLinkStrength(m_Start, m_End, m_StartPin->m_Strength); + const auto endStrength = easeLinkStrength(m_Start, m_End, m_EndPin->m_Strength); + const auto cp0 = m_Start + m_StartPin->m_Dir * startStrength; + const auto cp1 = m_End + m_EndPin->m_Dir * endStrength; + + ImCubicBezierPoints result; + result.P0 = m_Start; + result.P1 = cp0; + result.P2 = cp1; + result.P3 = m_End; + + return result; +} + +bool ed::Link::TestHit(const ImVec2& point, float extraThickness) const +{ + if (!m_IsLive) + return false; + + auto bounds = GetBounds(); + if (extraThickness > 0.0f) + bounds.Expand(extraThickness); + + if (!bounds.Contains(point)) + return false; + + const auto bezier = GetCurve(); + const auto result = ImProjectOnCubicBezier(point, bezier.P0, bezier.P1, bezier.P2, bezier.P3, 50); + + return result.Distance <= m_Thickness + extraThickness; +} + +bool ed::Link::TestHit(const ImRect& rect, bool allowIntersect) const +{ + if (!m_IsLive) + return false; + + const auto bounds = GetBounds(); + + if (rect.Contains(bounds)) + return true; + + if (!allowIntersect || !rect.Overlaps(bounds)) + return false; + + const auto bezier = GetCurve(); + + const auto p0 = rect.GetTL(); + const auto p1 = rect.GetTR(); + const auto p2 = rect.GetBR(); + const auto p3 = rect.GetBL(); + + if (ImCubicBezierLineIntersect(bezier.P0, bezier.P1, bezier.P2, bezier.P3, p0, p1).Count > 0) + return true; + if (ImCubicBezierLineIntersect(bezier.P0, bezier.P1, bezier.P2, bezier.P3, p1, p2).Count > 0) + return true; + if (ImCubicBezierLineIntersect(bezier.P0, bezier.P1, bezier.P2, bezier.P3, p2, p3).Count > 0) + return true; + if (ImCubicBezierLineIntersect(bezier.P0, bezier.P1, bezier.P2, bezier.P3, p3, p0).Count > 0) + return true; + + return false; +} + +ImRect ed::Link::GetBounds() const +{ + if (m_IsLive) + { + const auto curve = GetCurve(); + auto bounds = ImCubicBezierBoundingRect(curve.P0, curve.P1, curve.P2, curve.P3); + + if (bounds.GetWidth() == 0.0f) + { + bounds.Min.x -= 0.5f; + bounds.Max.x += 0.5f; + } + + if (bounds.GetHeight() == 0.0f) + { + bounds.Min.y -= 0.5f; + bounds.Max.y += 0.5f; + } + + if (m_StartPin->m_ArrowSize) + { + const auto start_dir = ImNormalized(ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, 0.0f)); + const auto p0 = curve.P0; + const auto p1 = curve.P0 - start_dir * m_StartPin->m_ArrowSize; + const auto min = ImMin(p0, p1); + const auto max = ImMax(p0, p1); + auto arrowBounds = ImRect(min, ImMax(max, min + ImVec2(1, 1))); + bounds.Add(arrowBounds); + } + + if (m_EndPin->m_ArrowSize) + { + const auto end_dir = ImNormalized(ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, 1.0f)); + const auto p0 = curve.P3; + const auto p1 = curve.P3 + end_dir * m_EndPin->m_ArrowSize; + const auto min = ImMin(p0, p1); + const auto max = ImMax(p0, p1); + auto arrowBounds = ImRect(min, ImMax(max, min + ImVec2(1, 1))); + bounds.Add(arrowBounds); + } + + return bounds; + } + else + return ImRect(); +} + + + + +//------------------------------------------------------------------------------ +// +// Editor Context +// +//------------------------------------------------------------------------------ +ed::EditorContext::EditorContext(const ax::NodeEditor::Config* config) + : m_Config(config) + , m_EditorActiveId(0) + , m_IsFirstFrame(true) + , m_IsFocused(false) + , m_IsHovered(false) + , m_IsHoveredWithoutOverlapp(false) + , m_ShortcutsEnabled(true) + , m_Style() + , m_Nodes() + , m_Pins() + , m_Links() + , m_SelectionId(1) + , m_LastActiveLink(nullptr) + , m_Canvas() + , m_IsCanvasVisible(false) + , m_NodeBuilder(this) + , m_HintBuilder(this) + , m_CurrentAction(nullptr) + , m_NavigateAction(this, m_Canvas) + , m_SizeAction(this) + , m_DragAction(this) + , m_SelectAction(this) + , m_ContextMenuAction(this) + , m_ShortcutAction(this) + , m_CreateItemAction(this) + , m_DeleteItemsAction(this) + , m_AnimationControllers{ &m_FlowAnimationController } + , m_FlowAnimationController(this) + , m_HoveredNode(0) + , m_HoveredPin(0) + , m_HoveredLink(0) + , m_DoubleClickedNode(0) + , m_DoubleClickedPin(0) + , m_DoubleClickedLink(0) + , m_BackgroundClickButtonIndex(-1) + , m_BackgroundDoubleClickButtonIndex(-1) + , m_IsInitialized(false) + , m_Settings() + , m_DrawList(nullptr) + , m_ExternalChannel(0) +{ +} + +ed::EditorContext::~EditorContext() +{ + if (m_IsInitialized) + SaveSettings(); + + for (auto link : m_Links) delete link.m_Object; + for (auto pin : m_Pins) delete pin.m_Object; + for (auto node : m_Nodes) delete node.m_Object; + + m_Splitter.ClearFreeMemory(); +} + +void ed::EditorContext::Begin(const char* id, const ImVec2& size) +{ + m_EditorActiveId = ImGui::GetID(id); + ImGui::PushID(id); + + auto availableContentSize = ImGui::GetContentRegionAvail(); + ImVec2 canvasSize = ImFloor(size); + if (canvasSize.x <= 0.0f) + canvasSize.x = ImMax(4.0f, availableContentSize.x); + if (canvasSize.y <= 0.0f) + canvasSize.y = ImMax(4.0f, availableContentSize.y); + + if (!m_IsInitialized) + { + // Cycle canvas, so it has a chance to initialize its size before settings are loaded + if (m_Canvas.Begin(id, canvasSize)) + m_Canvas.End(); + + LoadSettings(); + m_IsInitialized = true; + } + + //ImGui::LogToClipboard(); + //Log("---- begin ----"); + + static auto resetAndCollect = [](auto& objects) + { + objects.erase(std::remove_if(objects.begin(), objects.end(), [](auto objectWrapper) + { + if (objectWrapper->m_DeleteOnNewFrame) + { + delete objectWrapper.m_Object; + return true; + } + else + { + objectWrapper->Reset(); + return false; + } + }), objects.end()); + }; + + resetAndCollect(m_Nodes); + resetAndCollect(m_Pins); + resetAndCollect(m_Links); + + m_DrawList = ImGui::GetWindowDrawList(); + + ImDrawList_SwapSplitter(m_DrawList, m_Splitter); + m_ExternalChannel = m_DrawList->_Splitter._Current; + + if (m_CurrentAction && m_CurrentAction->IsDragging() && m_NavigateAction.MoveOverEdge(canvasSize)) + { + auto& io = ImGui::GetIO(); + auto offset = m_NavigateAction.GetMoveScreenOffset(); + for (int i = 0; i < 5; ++i) + io.MouseClickedPos[i] = io.MouseClickedPos[i] - offset; + } + else + m_NavigateAction.StopMoveOverEdge(); + + auto previousSize = m_Canvas.Rect().GetSize(); + auto previousVisibleRect = m_Canvas.ViewRect(); + m_IsCanvasVisible = m_Canvas.Begin(id, canvasSize); + + //ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); + //ImGui::BeginChild(id, size, false, + // ImGuiWindowFlags_NoMove | + // ImGuiWindowFlags_NoScrollbar | + // ImGuiWindowFlags_NoScrollWithMouse); + + m_IsFocused = ImGui::IsWindowFocused(); + + // + m_NavigateAction.SetWindow(m_Canvas.ViewRect().Min, m_Canvas.ViewRect().GetSize()); + + // Handle canvas size change. Scale to Y axis, center on X. + if (!ImRect_IsEmpty(previousVisibleRect) && previousSize != canvasSize) + { + m_NavigateAction.FinishNavigation(); + + auto centerX = (previousVisibleRect.Max.x + previousVisibleRect.Min.x) * 0.5f; + auto centerY = (previousVisibleRect.Max.y + previousVisibleRect.Min.y) * 0.5f; + auto currentVisibleRect = m_Canvas.ViewRect(); + auto currentAspectRatio = currentVisibleRect.GetHeight() ? (currentVisibleRect.GetWidth() / currentVisibleRect.GetHeight()) : 0.0f; + auto width = previousVisibleRect.GetWidth(); + auto height = previousVisibleRect.GetHeight(); + + if (m_Config.CanvasSizeMode == ax::NodeEditor::CanvasSizeMode::FitVerticalView) + { + height = previousVisibleRect.GetHeight(); + width = height * currentAspectRatio; + } + else if (m_Config.CanvasSizeMode == ax::NodeEditor::CanvasSizeMode::FitHorizontalView) + { + width = previousVisibleRect.GetWidth(); + height = width / currentAspectRatio; + } + else if (m_Config.CanvasSizeMode == ax::NodeEditor::CanvasSizeMode::CenterOnly) + { + width = currentVisibleRect.GetWidth(); + height = currentVisibleRect.GetHeight(); + } + + previousVisibleRect.Min.x = centerX - 0.5f * width; + previousVisibleRect.Max.x = centerX + 0.5f * width; + previousVisibleRect.Min.y = centerY - 0.5f * height; + previousVisibleRect.Max.y = centerY + 0.5f * height; + + m_NavigateAction.NavigateTo(previousVisibleRect, Detail::NavigateAction::ZoomMode::Exact, 0.0f); + } + + m_Canvas.SetView(m_NavigateAction.GetView()); + + // #debug #clip + //ImGui::Text("CLIP = { x=%g y=%g w=%g h=%g r=%g b=%g }", + // clipMin.x, clipMin.y, clipMax.x - clipMin.x, clipMax.y - clipMin.y, clipMax.x, clipMax.y); + + // Reserve channels for background and links + ImDrawList_ChannelsGrow(m_DrawList, c_NodeStartChannel); + + if (HasSelectionChanged()) + ++m_SelectionId; + + m_LastSelectedObjects = m_SelectedObjects; +} + +void ed::EditorContext::End() +{ + //auto& io = ImGui::GetIO(); + auto control = BuildControl(m_CurrentAction && m_CurrentAction->IsDragging()); // NavigateAction.IsMovingOverEdge() + //auto& editorStyle = GetStyle(); + + m_HoveredNode = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : 0; + m_HoveredPin = control.HotPin && m_CurrentAction == nullptr ? control.HotPin->m_ID : 0; + m_HoveredLink = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : 0; + m_DoubleClickedNode = control.DoubleClickedNode ? control.DoubleClickedNode->m_ID : 0; + m_DoubleClickedPin = control.DoubleClickedPin ? control.DoubleClickedPin->m_ID : 0; + m_DoubleClickedLink = control.DoubleClickedLink ? control.DoubleClickedLink->m_ID : 0; + m_BackgroundClickButtonIndex = control.BackgroundClickButtonIndex; + m_BackgroundDoubleClickButtonIndex = control.BackgroundDoubleClickButtonIndex; + + //if (DoubleClickedNode) LOG_TRACE(0, "DOUBLE CLICK NODE: %d", DoubleClickedNode); + //if (DoubleClickedPin) LOG_TRACE(0, "DOUBLE CLICK PIN: %d", DoubleClickedPin); + //if (DoubleClickedLink) LOG_TRACE(0, "DOUBLE CLICK LINK: %d", DoubleClickedLink); + //if (BackgroundDoubleClicked) LOG_TRACE(0, "DOUBLE CLICK BACKGROUND", DoubleClickedLink); + + const bool isSelecting = m_CurrentAction && m_CurrentAction->AsSelect() != nullptr; + const bool isDragging = m_CurrentAction && m_CurrentAction->AsDrag() != nullptr; + //const bool isSizing = CurrentAction && CurrentAction->AsSize() != nullptr; + + // Draw nodes + for (auto node : m_Nodes) + if (node->m_IsLive && node->IsVisible()) + node->Draw(m_DrawList); + + // Draw links + for (auto link : m_Links) + if (link->m_IsLive && link->IsVisible()) + link->Draw(m_DrawList); + + // Highlight selected objects + { + auto selectedObjects = &m_SelectedObjects; + if (auto selectAction = m_CurrentAction ? m_CurrentAction->AsSelect() : nullptr) + selectedObjects = &selectAction->m_CandidateObjects; + + for (auto selectedObject : *selectedObjects) + { + if (selectedObject->IsVisible()) + selectedObject->Draw(m_DrawList, Object::Selected); + } + + // Highlight adjacent links + static auto isLinkHighlightedForPin = [](const Pin& pin) + { + return pin.m_Node->m_HighlightConnectedLinks && pin.m_Node->m_IsSelected; + }; + + for (auto& link : m_Links) + { + if (!link->m_IsLive || !link->IsVisible()) + continue; + + auto isLinkHighlighted = isLinkHighlightedForPin(*link->m_StartPin) || isLinkHighlightedForPin(*link->m_EndPin); + if (!isLinkHighlighted) + continue; + + link->Draw(m_DrawList, Object::Highlighted); + } + } + + if (!isSelecting) + { + auto hoveredObject = control.HotObject; + if (auto dragAction = m_CurrentAction ? m_CurrentAction->AsDrag() : nullptr) + hoveredObject = dragAction->m_DraggedObject; + if (auto sizeAction = m_CurrentAction ? m_CurrentAction->AsSize() : nullptr) + hoveredObject = sizeAction->m_SizedNode; + + if (hoveredObject && !IsSelected(hoveredObject) && hoveredObject->IsVisible()) + hoveredObject->Draw(m_DrawList, Object::Hovered); + } + + // Draw animations + for (auto controller : m_AnimationControllers) + controller->Draw(m_DrawList); + + if (m_CurrentAction && !m_CurrentAction->Process(control)) + m_CurrentAction = nullptr; + + if (m_NavigateAction.m_IsActive) + m_NavigateAction.Process(control); + else + m_NavigateAction.Accept(control); + + if (nullptr == m_CurrentAction) + { + EditorAction* possibleAction = nullptr; + + auto accept = [&possibleAction, &control](EditorAction& action) + { + auto result = action.Accept(control); + + if (result == EditorAction::True) + return true; + else if (/*!possibleAction &&*/ result == EditorAction::Possible) + possibleAction = &action; + else if (result == EditorAction::Possible) + action.Reject(); + + return false; + }; + + if (accept(m_ContextMenuAction)) + m_CurrentAction = &m_ContextMenuAction; + else if (accept(m_ShortcutAction)) + m_CurrentAction = &m_ShortcutAction; + else if (accept(m_SizeAction)) + m_CurrentAction = &m_SizeAction; + else if (accept(m_DragAction)) + m_CurrentAction = &m_DragAction; + else if (accept(m_CreateItemAction)) + m_CurrentAction = &m_CreateItemAction; + else if (accept(m_DeleteItemsAction)) + m_CurrentAction = &m_DeleteItemsAction; + else if (accept(m_SelectAction)) + m_CurrentAction = &m_SelectAction; + + if (possibleAction) + ImGui::SetMouseCursor(possibleAction->GetCursor()); + + if (m_CurrentAction && possibleAction) + possibleAction->Reject(); + } + + if (m_CurrentAction) + ImGui::SetMouseCursor(m_CurrentAction->GetCursor()); + + // Draw selection rectangle + m_SelectAction.Draw(m_DrawList); + + bool sortGroups = false; + if (control.ActiveNode) + { + if (!IsGroup(control.ActiveNode)) + { + // Bring active node to front + auto activeNodeIt = std::find(m_Nodes.begin(), m_Nodes.end(), control.ActiveNode); + std::rotate(activeNodeIt, activeNodeIt + 1, m_Nodes.end()); + } + else if (!isDragging && m_CurrentAction && m_CurrentAction->AsDrag()) + { + // Bring content of dragged group to front + std::vector nodes; + control.ActiveNode->GetGroupedNodes(nodes); + + std::stable_partition(m_Nodes.begin(), m_Nodes.end(), [&nodes](Node* node) + { + return std::find(nodes.begin(), nodes.end(), node) == nodes.end(); + }); + + sortGroups = true; + } + } + + // Sort nodes if bounds of node changed + if (sortGroups || ((m_Settings.m_DirtyReason & (SaveReasonFlags::Position | SaveReasonFlags::Size)) != SaveReasonFlags::None)) + { + // Bring all groups before regular nodes + auto groupsItEnd = std::stable_partition(m_Nodes.begin(), m_Nodes.end(), IsGroup); + + // Sort groups by area + std::sort(m_Nodes.begin(), groupsItEnd, [this](Node* lhs, Node* rhs) + { + const auto& lhsSize = lhs == m_SizeAction.m_SizedNode ? m_SizeAction.GetStartGroupBounds().GetSize() : lhs->m_GroupBounds.GetSize(); + const auto& rhsSize = rhs == m_SizeAction.m_SizedNode ? m_SizeAction.GetStartGroupBounds().GetSize() : rhs->m_GroupBounds.GetSize(); + + const auto lhsArea = lhsSize.x * lhsSize.y; + const auto rhsArea = rhsSize.x * rhsSize.y; + + return lhsArea > rhsArea; + }); + } + + // Apply Z order + std::stable_sort(m_Nodes.begin(), m_Nodes.end(), [](const auto& lhs, const auto& rhs) + { + return lhs->m_ZPosition < rhs->m_ZPosition; + }); + +# if 1 + // Every node has few channels assigned. Grow channel list + // to hold twice as much of channels and place them in + // node drawing order. + { + // Copy group nodes + auto liveNodeCount = static_cast(std::count_if(m_Nodes.begin(), m_Nodes.end(), [](Node* node) { return node->m_IsLive; })); + + // Reserve two additional channels for sorted list of channels + auto nodeChannelCount = m_DrawList->_Splitter._Count; + ImDrawList_ChannelsGrow(m_DrawList, m_DrawList->_Splitter._Count + c_ChannelsPerNode * liveNodeCount + c_LinkChannelCount); + + int targetChannel = nodeChannelCount; + + auto copyNode = [this, &targetChannel](Node* node) + { + if (!node->m_IsLive) + return; + + for (int i = 0; i < c_ChannelsPerNode; ++i) + ImDrawList_SwapChannels(m_DrawList, node->m_Channel + i, targetChannel + i); + + node->m_Channel = targetChannel; + targetChannel += c_ChannelsPerNode; + }; + + auto groupsItEnd = std::find_if(m_Nodes.begin(), m_Nodes.end(), [](Node* node) { return !IsGroup(node); }); + + // Copy group nodes + std::for_each(m_Nodes.begin(), groupsItEnd, copyNode); + + // Copy links + for (int i = 0; i < c_LinkChannelCount; ++i, ++targetChannel) + ImDrawList_SwapChannels(m_DrawList, c_LinkStartChannel + i, targetChannel); + + // Copy normal nodes + std::for_each(groupsItEnd, m_Nodes.end(), copyNode); + } +# endif + + // ImGui::PopClipRect(); + + // Draw grid +# if 1 // #FIXME + { + //auto& style = ImGui::GetStyle(); + + m_DrawList->ChannelsSetCurrent(c_UserChannel_Grid); + + ImVec2 offset = m_Canvas.ViewOrigin() * (1.0f / m_Canvas.ViewScale()); + ImU32 GRID_COLOR = GetColor(StyleColor_Grid, ImClamp(m_Canvas.ViewScale() * m_Canvas.ViewScale(), 0.0f, 1.0f)); + float GRID_SX = 32.0f;// * m_Canvas.ViewScale(); + float GRID_SY = 32.0f;// * m_Canvas.ViewScale(); + ImVec2 VIEW_POS = m_Canvas.ViewRect().Min; + ImVec2 VIEW_SIZE = m_Canvas.ViewRect().GetSize(); + + m_DrawList->AddRectFilled(VIEW_POS, VIEW_POS + VIEW_SIZE, GetColor(StyleColor_Bg)); + + for (float x = fmodf(offset.x, GRID_SX); x < VIEW_SIZE.x; x += GRID_SX) + m_DrawList->AddLine(ImVec2(x, 0.0f) + VIEW_POS, ImVec2(x, VIEW_SIZE.y) + VIEW_POS, GRID_COLOR); + for (float y = fmodf(offset.y, GRID_SY); y < VIEW_SIZE.y; y += GRID_SY) + m_DrawList->AddLine(ImVec2(0.0f, y) + VIEW_POS, ImVec2(VIEW_SIZE.x, y) + VIEW_POS, GRID_COLOR); + } +# endif + +# if 0 + { + auto userChannel = drawList->_Splitter._Count; + auto channelsToCopy = c_UserLayersCount; + ImDrawList_ChannelsGrow(drawList, userChannel + channelsToCopy); + for (int i = 0; i < channelsToCopy; ++i) + ImDrawList_SwapChannels(drawList, userChannel + i, c_UserLayerChannelStart + i); + } +# endif + +# if 0 + { + auto preOffset = ImVec2(0, 0); + auto postOffset = m_OldCanvas.WindowScreenPos + m_OldCanvas.ClientOrigin; + auto scale = m_OldCanvas.Zoom; + + ImDrawList_TransformChannels(drawList, 0, 1, preOffset, scale, postOffset); + ImDrawList_TransformChannels(drawList, c_BackgroundChannelStart, drawList->_ChannelsCount - 1, preOffset, scale, postOffset); + + auto clipTranslation = m_OldCanvas.WindowScreenPos - m_OldCanvas.FromScreen(m_OldCanvas.WindowScreenPos); + ImGui::PushClipRect(m_OldCanvas.WindowScreenPos + ImVec2(1, 1), m_OldCanvas.WindowScreenPos + m_OldCanvas.WindowScreenSize - ImVec2(1, 1), false); + ImDrawList_TranslateAndClampClipRects(drawList, 0, 1, clipTranslation); + ImDrawList_TranslateAndClampClipRects(drawList, c_BackgroundChannelStart, drawList->_ChannelsCount - 1, clipTranslation); + ImGui::PopClipRect(); + + // #debug: Static grid in local space + //for (float x = 0; x < Canvas.WindowScreenSize.x; x += 100) + // drawList->AddLine(ImVec2(x, 0.0f) + Canvas.WindowScreenPos, ImVec2(x, Canvas.WindowScreenSize.y) + Canvas.WindowScreenPos, IM_COL32(255, 0, 0, 128)); + //for (float y = 0; y < Canvas.WindowScreenSize.y; y += 100) + // drawList->AddLine(ImVec2(0.0f, y) + Canvas.WindowScreenPos, ImVec2(Canvas.WindowScreenSize.x, y) + Canvas.WindowScreenPos, IM_COL32(255, 0, 0, 128)); + } +# endif + +# if 1 + // Move user and hint channels to top + { + // Clip plane is transformed to global space. + // These channels already have clip planes in global space, so + // we move them to clip plane. Batch transformation in canvas + // will bring them back to global space. + auto preTransformClipRect = [this](int channelIndex) + { + ImDrawChannel& channel = m_DrawList->_Splitter._Channels[channelIndex]; + for (ImDrawCmd& cmd : channel._CmdBuffer) + { + auto a = ToCanvas(ImVec2(cmd.ClipRect.x, cmd.ClipRect.y)); + auto b = ToCanvas(ImVec2(cmd.ClipRect.z, cmd.ClipRect.w)); + cmd.ClipRect = ImVec4(a.x, a.y, b.x, b.y); + } + }; + + m_DrawList->ChannelsSetCurrent(0); + + auto channelCount = m_DrawList->_Splitter._Count; + ImDrawList_ChannelsGrow(m_DrawList, channelCount + 3); + ImDrawList_SwapChannels(m_DrawList, c_UserChannel_HintsBackground, channelCount + 0); + ImDrawList_SwapChannels(m_DrawList, c_UserChannel_Hints, channelCount + 1); + ImDrawList_SwapChannels(m_DrawList, c_UserChannel_Content, channelCount + 2); + + preTransformClipRect(channelCount + 0); + preTransformClipRect(channelCount + 1); + preTransformClipRect(channelCount + 2); + } +# endif + + UpdateAnimations(); + + m_DrawList->ChannelsMerge(); + + // #debug + // drawList->AddRectFilled(ImVec2(-10.0f, -10.0f), ImVec2(10.0f, 10.0f), IM_COL32(255, 0, 255, 255)); + + // ImGui::EndChild(); + // ImGui::PopStyleColor(); + if (m_IsCanvasVisible) + m_Canvas.End(); + + ImDrawList_SwapSplitter(m_DrawList, m_Splitter); + + // Draw border + { + auto& style = ImGui::GetStyle(); + auto borderShadoColor = style.Colors[ImGuiCol_BorderShadow]; + auto borderColor = style.Colors[ImGuiCol_Border]; + m_DrawList->AddRect(m_Canvas.Rect().Min + ImVec2(1, 1), m_Canvas.Rect().Max - ImVec2(1, 1), ImColor(borderShadoColor)); + m_DrawList->AddRect(m_Canvas.Rect().Min, m_Canvas.Rect().Max, ImColor(borderColor)); + } + + // #metrics + // ShowMetrics(control); + + ImGui::PopID(); + + if (!m_CurrentAction && m_IsFirstFrame && !m_Settings.m_Selection.empty()) + { + ClearSelection(); + for (auto id : m_Settings.m_Selection) + if (auto object = FindObject(id)) + SelectObject(object); + } + + if (HasSelectionChanged()) + MakeDirty(SaveReasonFlags::Selection); + + if (m_Settings.m_IsDirty && !m_CurrentAction) + SaveSettings(); + + m_DrawList = nullptr; + m_IsFirstFrame = false; +} + +bool ed::EditorContext::DoLink(LinkId id, PinId startPinId, PinId endPinId, ImU32 color, float thickness) +{ + //auto& editorStyle = GetStyle(); + + auto startPin = FindPin(startPinId); + auto endPin = FindPin(endPinId); + + if (!startPin || !startPin->m_IsLive || !endPin || !endPin->m_IsLive) + return false; + + startPin->m_HasConnection = true; + endPin->m_HasConnection = true; + + auto link = GetLink(id); + link->m_StartPin = startPin; + link->m_EndPin = endPin; + link->m_Color = color; + link->m_HighlightColor= GetColor(StyleColor_HighlightLinkBorder); + link->m_Thickness = thickness; + link->m_IsLive = true; + + link->UpdateEndpoints(); + + return true; +} + +void ed::EditorContext::SetNodePosition(NodeId nodeId, const ImVec2& position) +{ + auto node = FindNode(nodeId); + if (!node) + { + node = CreateNode(nodeId); + node->m_IsLive = false; + } + + if (node->m_Bounds.Min != position) + { + node->m_Bounds.Translate(position - node->m_Bounds.Min); + FloorRect(node->m_Bounds); + MakeDirty(NodeEditor::SaveReasonFlags::Position, node); + } +} + +void ed::EditorContext::SetGroupSize(NodeId nodeId, const ImVec2& size) +{ + auto node = FindNode(nodeId); + if (!node) + { + node = CreateNode(nodeId); + node->m_IsLive = false; + } + + node->m_Type = NodeType::Group; + + if (node->m_GroupBounds.GetSize() != size) + { + node->m_GroupBounds.Min = node->m_Bounds.Min; + node->m_GroupBounds.Max = node->m_Bounds.Min + size; + FloorRect(node->m_GroupBounds); + MakeDirty(NodeEditor::SaveReasonFlags::Size, node); + } +} + +ImVec2 ed::EditorContext::GetNodePosition(NodeId nodeId) +{ + auto node = FindNode(nodeId); + if (!node) + return ImVec2(FLT_MAX, FLT_MAX); + + return node->m_Bounds.Min; +} + +ImVec2 ed::EditorContext::GetNodeSize(NodeId nodeId) +{ + auto node = FindNode(nodeId); + if (!node) + return ImVec2(0, 0); + + return node->m_Bounds.GetSize(); +} + +void ed::EditorContext::SetNodeZPosition(NodeId nodeId, float z) +{ + auto node = FindNode(nodeId); + if (!node) + { + node = CreateNode(nodeId); + node->m_IsLive = false; + } + + node->m_ZPosition = z; +} + +float ed::EditorContext::GetNodeZPosition(NodeId nodeId) +{ + auto node = FindNode(nodeId); + if (!node) + return 0.0f; + + return node->m_ZPosition; +} + +void ed::EditorContext::MarkNodeToRestoreState(Node* node) +{ + node->m_RestoreState = true; +} + +void ed::EditorContext::UpdateNodeState(Node* node) +{ + bool tryLoadState = node->m_RestoreState; + + node->m_RestoreState = false; + + auto settings = m_Settings.FindNode(node->m_ID); + if (!settings) + return; + + if (!tryLoadState && settings->m_WasUsed) + return; + + if (!settings->m_WasUsed) + { + MakeDirty(SaveReasonFlags::AddNode, node); + settings->m_WasUsed = true; + } + + // Load state from config (if possible) + if (tryLoadState) + { + NodeSettings newSettings = *settings; + if (NodeSettings::Parse(m_Config.LoadNode(node->m_ID), newSettings)) + *settings = newSettings; + } + + node->m_Bounds.Min = settings->m_Location; + node->m_Bounds.Max = node->m_Bounds.Min + settings->m_Size; + FloorRect(node->m_Bounds); + node->m_GroupBounds.Min = settings->m_Location; + node->m_GroupBounds.Max = node->m_GroupBounds.Min + settings->m_GroupSize; + FloorRect(node->m_GroupBounds); +} + +void ed::EditorContext::RemoveSettings(Object* object) +{ + if (auto node = object->AsNode()) + { + m_Settings.RemoveNode(node->m_ID); + MakeDirty(SaveReasonFlags::RemoveNode, node); + } +} + +void ed::EditorContext::ClearSelection() +{ + for (auto& object : m_SelectedObjects) + object->m_IsSelected = false; + + m_SelectedObjects.clear(); +} + +void ed::EditorContext::SelectObject(Object* object) +{ + m_SelectedObjects.push_back(object); + object->m_IsSelected = true; +} + +void ed::EditorContext::DeselectObject(Object* object) +{ + auto objectIt = std::find(m_SelectedObjects.begin(), m_SelectedObjects.end(), object); + if (objectIt == m_SelectedObjects.end()) + return; + + object->m_IsSelected = false; + m_SelectedObjects.erase(objectIt); +} + +void ed::EditorContext::SetSelectedObject(Object* object) +{ + ClearSelection(); + SelectObject(object); +} + +void ed::EditorContext::ToggleObjectSelection(Object* object) +{ + if (IsSelected(object)) + DeselectObject(object); + else + SelectObject(object); +} + +bool ed::EditorContext::IsSelected(Object* object) +{ + return object && object->m_IsSelected; + // return std::find(m_SelectedObjects.begin(), m_SelectedObjects.end(), object) != m_SelectedObjects.end(); +} + +const ed::vector& ed::EditorContext::GetSelectedObjects() +{ + return m_SelectedObjects; +} + +bool ed::EditorContext::IsAnyNodeSelected() +{ + for (auto object : m_SelectedObjects) + if (object->AsNode()) + return true; + + return false; +} + +bool ed::EditorContext::IsAnyLinkSelected() +{ + for (auto object : m_SelectedObjects) + if (object->AsLink()) + return true; + + return false; +} + +bool ed::EditorContext::HasSelectionChanged() +{ + return m_LastSelectedObjects != m_SelectedObjects; +} + +ed::Node* ed::EditorContext::FindNodeAt(const ImVec2& p) +{ + for (auto node : m_Nodes) + if (node->TestHit(p)) + return node; + + return nullptr; +} + +void ed::EditorContext::FindNodesInRect(const ImRect& r, vector& result, bool append, bool includeIntersecting) +{ + if (!append) + result.resize(0); + + if (ImRect_IsEmpty(r)) + return; + + for (auto node : m_Nodes) + if (node->TestHit(r, includeIntersecting)) + result.push_back(node); +} + +void ed::EditorContext::FindLinksInRect(const ImRect& r, vector& result, bool append) +{ + if (!append) + result.resize(0); + + if (ImRect_IsEmpty(r)) + return; + + for (auto link : m_Links) + if (link->TestHit(r)) + result.push_back(link); +} + +bool ed::EditorContext::HasAnyLinks(NodeId nodeId) const +{ + for (auto link : m_Links) + { + if (!link->m_IsLive) + continue; + + if (link->m_StartPin->m_Node->m_ID == nodeId || link->m_EndPin->m_Node->m_ID == nodeId) + return true; + } + + return false; +} + +bool ed::EditorContext::HasAnyLinks(PinId pinId) const +{ + for (auto link : m_Links) + { + if (!link->m_IsLive) + continue; + + if (link->m_StartPin->m_ID == pinId || link->m_EndPin->m_ID == pinId) + return true; + } + + return false; +} + +int ed::EditorContext::BreakLinks(NodeId nodeId) +{ + int result = 0; + for (auto link : m_Links) + { + if (!link->m_IsLive) + continue; + + if (link->m_StartPin->m_Node->m_ID == nodeId || link->m_EndPin->m_Node->m_ID == nodeId) + { + if (GetItemDeleter().Add(link)) + ++result; + } + } + return result; +} + +int ed::EditorContext::BreakLinks(PinId pinId) +{ + int result = 0; + for (auto link : m_Links) + { + if (!link->m_IsLive) + continue; + + if (link->m_StartPin->m_ID == pinId || link->m_EndPin->m_ID == pinId) + { + if (GetItemDeleter().Add(link)) + ++result; + } + } + return result; +} + +void ed::EditorContext::FindLinksForNode(NodeId nodeId, vector& result, bool add) +{ + if (!add) + result.clear(); + + for (auto link : m_Links) + { + if (!link->m_IsLive) + continue; + + if (link->m_StartPin->m_Node->m_ID == nodeId || link->m_EndPin->m_Node->m_ID == nodeId) + result.push_back(link); + } +} + +bool ed::EditorContext::PinHadAnyLinks(PinId pinId) +{ + auto pin = FindPin(pinId); + if (!pin || !pin->m_IsLive) + return false; + + return pin->m_HasConnection || pin->m_HadConnection; +} + +void ed::EditorContext::NotifyLinkDeleted(Link* link) +{ + if (m_LastActiveLink == link) + m_LastActiveLink = nullptr; +} + +void ed::EditorContext::Suspend(SuspendFlags flags) +{ + IM_ASSERT(m_DrawList != nullptr && "Suspend was called outiside of Begin/End."); + auto lastChannel = m_DrawList->_Splitter._Current; + m_DrawList->ChannelsSetCurrent(m_ExternalChannel); + if (m_IsCanvasVisible) + m_Canvas.Suspend(); + m_DrawList->ChannelsSetCurrent(lastChannel); + if ((flags & SuspendFlags::KeepSplitter) != SuspendFlags::KeepSplitter) + ImDrawList_SwapSplitter(m_DrawList, m_Splitter); +} + +void ed::EditorContext::Resume(SuspendFlags flags) +{ + IM_ASSERT(m_DrawList != nullptr && "Reasume was called outiside of Begin/End."); + if ((flags & SuspendFlags::KeepSplitter) != SuspendFlags::KeepSplitter) + ImDrawList_SwapSplitter(m_DrawList, m_Splitter); + auto lastChannel = m_DrawList->_Splitter._Current; + m_DrawList->ChannelsSetCurrent(m_ExternalChannel); + if (m_IsCanvasVisible) + m_Canvas.Resume(); + m_DrawList->ChannelsSetCurrent(lastChannel); +} + +bool ed::EditorContext::IsSuspended() +{ + return m_Canvas.IsSuspended(); +} + +bool ed::EditorContext::IsFocused() +{ + return m_IsFocused; +} + +bool ed::EditorContext::IsHovered() const +{ + return m_IsHovered; +} + +bool ed::EditorContext::IsHoveredWithoutOverlapp() const +{ + return m_IsHoveredWithoutOverlapp; +} + +bool ed::EditorContext::CanAcceptUserInput() const +{ + return m_IsFocused && m_IsHovered; +} + +int ed::EditorContext::CountLiveNodes() const +{ + return (int)std::count_if(m_Nodes.begin(), m_Nodes.end(), [](const Node* node) { return node->m_IsLive; }); +} + +int ed::EditorContext::CountLivePins() const +{ + return (int)std::count_if(m_Pins.begin(), m_Pins.end(), [](const Pin* pin) { return pin->m_IsLive; }); +} + +int ed::EditorContext::CountLiveLinks() const +{ + return (int)std::count_if(m_Links.begin(), m_Links.end(), [](const Link* link) { return link->m_IsLive; }); +} + +ed::Pin* ed::EditorContext::CreatePin(PinId id, PinKind kind) +{ + IM_ASSERT(nullptr == FindObject(id)); + auto pin = new Pin(this, id, kind); + m_Pins.push_back({id, pin}); + std::sort(m_Pins.begin(), m_Pins.end()); + return pin; +} + +ed::Node* ed::EditorContext::CreateNode(NodeId id) +{ + IM_ASSERT(nullptr == FindObject(id)); + auto node = new Node(this, id); + m_Nodes.push_back({id, node}); + //std::sort(Nodes.begin(), Nodes.end()); + + auto settings = m_Settings.FindNode(id); + if (!settings) + settings = m_Settings.AddNode(id); + + UpdateNodeState(node); + + if (settings->m_GroupSize.x > 0 || settings->m_GroupSize.y > 0) + node->m_Type = NodeType::Group; + + node->m_IsLive = false; + + return node; +} + +ed::Link* ed::EditorContext::CreateLink(LinkId id) +{ + IM_ASSERT(nullptr == FindObject(id)); + auto link = new Link(this, id); + m_Links.push_back({id, link}); + std::sort(m_Links.begin(), m_Links.end()); + + return link; +} + +template +static inline auto FindItemInLinear(C& container, Id id) +{ +# if defined(_DEBUG) + auto start = container.data(); + auto end = container.data() + container.size(); + for (auto it = start; it < end; ++it) + if ((*it).m_ID == id) + return it->m_Object; +# else + for (auto item : container) + if (item.m_ID == id) + return item.m_Object; +# endif + + return static_cast(nullptr); +} + +template +static inline auto FindItemIn(C& container, Id id) +{ +//# if defined(_DEBUG) +// auto start = container.data(); +// auto end = container.data() + container.size(); +// for (auto it = start; it < end; ++it) +// if ((*it)->ID == id) +// return *it; +//# else +// for (auto item : container) +// if (item->ID == id) +// return item; +//# endif + auto key = typename C::value_type{ id, nullptr }; + auto first = container.cbegin(); + auto last = container.cend(); + auto it = std::lower_bound(first, last, key); + if (it != last && (key.m_ID == it->m_ID)) + return it->m_Object; + else + return static_castm_Object)>(nullptr); +} + +ed::Node* ed::EditorContext::FindNode(NodeId id) +{ + return FindItemInLinear(m_Nodes, id); +} + +ed::Pin* ed::EditorContext::FindPin(PinId id) +{ + return FindItemIn(m_Pins, id); +} + +ed::Link* ed::EditorContext::FindLink(LinkId id) +{ + return FindItemIn(m_Links, id); +} + +ed::Object* ed::EditorContext::FindObject(ObjectId id) +{ + if (id.IsNodeId()) + return FindNode(id.AsNodeId()); + else if (id.IsLinkId()) + return FindLink(id.AsLinkId()); + else if (id.IsPinId()) + return FindPin(id.AsPinId()); + else + return nullptr; +} + +ed::Node* ed::EditorContext::GetNode(NodeId id) +{ + auto node = FindNode(id); + if (!node) + node = CreateNode(id); + return node; +} + +ed::Pin* ed::EditorContext::GetPin(PinId id, PinKind kind) +{ + if (auto pin = FindPin(id)) + { + pin->m_Kind = kind; + return pin; + } + else + return CreatePin(id, kind); +} + +ed::Link* ed::EditorContext::GetLink(LinkId id) +{ + if (auto link = FindLink(id)) + return link; + else + return CreateLink(id); +} + +void ed::EditorContext::LoadSettings() +{ + ed::Settings::Parse(m_Config.Load(), m_Settings); + + if (ImRect_IsEmpty(m_Settings.m_VisibleRect)) + { + m_NavigateAction.m_Scroll = m_Settings.m_ViewScroll; + m_NavigateAction.m_Zoom = m_Settings.m_ViewZoom; + } + else + { + m_NavigateAction.NavigateTo(m_Settings.m_VisibleRect, NavigateAction::ZoomMode::Exact, 0.0f); + } +} + +void ed::EditorContext::SaveSettings() +{ + m_Config.BeginSave(); + + for (auto& node : m_Nodes) + { + auto settings = m_Settings.FindNode(node->m_ID); + settings->m_Location = node->m_Bounds.Min; + settings->m_Size = node->m_Bounds.GetSize(); + if (IsGroup(node)) + settings->m_GroupSize = node->m_GroupBounds.GetSize(); + + if (!node->m_RestoreState && settings->m_IsDirty && m_Config.SaveNodeSettings) + { + if (m_Config.SaveNode(node->m_ID, settings->Serialize().dump(), settings->m_DirtyReason)) + settings->ClearDirty(); + } + } + + m_Settings.m_Selection.resize(0); + for (auto& object : m_SelectedObjects) + m_Settings.m_Selection.push_back(object->ID()); + + m_Settings.m_ViewScroll = m_NavigateAction.m_Scroll; + m_Settings.m_ViewZoom = m_NavigateAction.m_Zoom; + m_Settings.m_VisibleRect = m_NavigateAction.m_VisibleRect; + + if (m_Config.Save(m_Settings.Serialize(), m_Settings.m_DirtyReason)) + m_Settings.ClearDirty(); + + m_Config.EndSave(); +} + +void ed::EditorContext::MakeDirty(SaveReasonFlags reason) +{ + m_Settings.MakeDirty(reason); +} + +void ed::EditorContext::MakeDirty(SaveReasonFlags reason, Node* node) +{ + m_Settings.MakeDirty(reason, node); +} + +ed::Link* ed::EditorContext::FindLinkAt(const ImVec2& p) +{ + for (auto& link : m_Links) + if (link->TestHit(p, c_LinkSelectThickness)) + return link; + + return nullptr; +} + +ImU32 ed::EditorContext::GetColor(StyleColor colorIndex) const +{ + return ImColor(m_Style.Colors[colorIndex]); +} + +ImU32 ed::EditorContext::GetColor(StyleColor colorIndex, float alpha) const +{ + auto color = m_Style.Colors[colorIndex]; + return ImColor(color.x, color.y, color.z, color.w * alpha); +} + +int ed::EditorContext::GetNodeIds(NodeId* nodes, int size) const +{ + if (size <= 0) + return 0; + + int result = 0; + for (auto node : m_Nodes) + { + if (!node->m_IsLive) + continue; + + *nodes++ = node->m_ID; + ++result; + if (--size == 0) + break; + } + + return result; +} + +void ed::EditorContext::RegisterAnimation(Animation* animation) +{ + m_LiveAnimations.push_back(animation); +} + +void ed::EditorContext::UnregisterAnimation(Animation* animation) +{ + auto it = std::find(m_LiveAnimations.begin(), m_LiveAnimations.end(), animation); + if (it != m_LiveAnimations.end()) + m_LiveAnimations.erase(it); +} + +void ed::EditorContext::UpdateAnimations() +{ + m_LastLiveAnimations = m_LiveAnimations; + + for (auto animation : m_LastLiveAnimations) + { + const bool isLive = (std::find(m_LiveAnimations.begin(), m_LiveAnimations.end(), animation) != m_LiveAnimations.end()); + + if (isLive) + animation->Update(); + } +} + +void ed::EditorContext::Flow(Link* link, FlowDirection direction) +{ + m_FlowAnimationController.Flow(link, direction); +} + +void ed::EditorContext::SetUserContext(bool globalSpace) +{ + const auto mousePos = ImGui::GetMousePos(); + + // Move drawing cursor to mouse location and prepare layer for + // content added by user. + if (globalSpace) + ImGui::SetCursorScreenPos(m_Canvas.FromLocal(mousePos)); + else + ImGui::SetCursorScreenPos(m_Canvas.FromLocal(mousePos)); + //ImGui::SetCursorScreenPos(ImFloor(mousePos)); + //ImGui::SetCursorScreenPos(ImVec2(floorf(mousePos.x), floorf(mousePos.y))); + + if (!IsSuspended()) + { + m_DrawList->ChannelsSetCurrent(c_UserChannel_Content); + } + + // #debug + // drawList->AddCircleFilled(ImGui::GetMousePos(), 4, IM_COL32(0, 255, 0, 255)); +} + +void ed::EditorContext::EnableShortcuts(bool enable) +{ + m_ShortcutsEnabled = enable; +} + +bool ed::EditorContext::AreShortcutsEnabled() +{ + return m_ShortcutsEnabled; +} + +ed::Control ed::EditorContext::BuildControl(bool allowOffscreen) +{ + m_IsHovered = false; + m_IsHoveredWithoutOverlapp = false; + + const auto windowHovered = ImGui::IsWindowHovered(); + const auto widgetHovered = ImGui::IsMouseHoveringRect(m_Canvas.ViewRect().Min, m_Canvas.ViewRect().Max, true); + + if (!allowOffscreen && !windowHovered && !widgetHovered) + return Control(); + + const auto mousePos = ImGui::GetMousePos(); + + // Expand clip rectangle to always contain cursor + auto editorRect = m_Canvas.ViewRect(); + auto isMouseOffscreen = allowOffscreen && !editorRect.Contains(mousePos); + if (isMouseOffscreen) + { + // Extend clip rect to capture off-screen mouse cursor + editorRect.Add(ImFloor(mousePos)); + editorRect.Add(ImVec2(ImCeil(mousePos.x), ImCeil(mousePos.y))); + + ImGui::PushClipRect(editorRect.Min, editorRect.Max, false); + } + + ImGuiID activeId = 0; + Object* hotObject = nullptr; + Object* activeObject = nullptr; + Object* clickedObject = nullptr; + Object* doubleClickedObject = nullptr; + + ImGuiButtonFlags extraFlags = ImGuiButtonFlags_None; + extraFlags |= ImGuiButtonFlags_MouseButtonLeft; + extraFlags |= ImGuiButtonFlags_MouseButtonRight; + extraFlags |= ImGuiButtonFlags_MouseButtonMiddle; + + static auto invisibleButtonEx = [](const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags extraFlags) -> int + { + using namespace ImGui; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return -1; + + if (size_arg.x == 0.0f || size_arg.y == 0.0f) + return false; + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(size); + if (!ItemAdd(bb, id)) + return -1; + + auto buttonIndex = ImGui::GetCurrentContext()->ActiveIdMouseButton; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, extraFlags); + + return pressed ? buttonIndex : -1; + }; + + // Emits invisible button and returns true if it is clicked. + auto emitInteractiveAreaEx = [&activeId](ObjectId id, const ImRect& rect, ImGuiButtonFlags extraFlags) -> int + { + char idString[33] = { 0 }; // itoa can output 33 bytes maximum + snprintf(idString, 32, "%p", id.AsPointer()); + ImGui::SetCursorScreenPos(rect.Min); + + // debug + //if (id < 0) return ImGui::Button(idString, to_imvec(rect.size)); + + auto buttonIndex = invisibleButtonEx(idString, rect.GetSize(), extraFlags); + + // #debug + //ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64)); + + if (ImGui::IsItemActive()) + activeId = ImGui::GetActiveID(); + + return buttonIndex; + }; + + auto emitInteractiveArea = [&emitInteractiveAreaEx, extraFlags](ObjectId id, const ImRect& rect) + { + return emitInteractiveAreaEx(id, rect, extraFlags); + }; + + // Check input interactions over area. + auto checkInteractionsInArea = [this, &emitInteractiveArea, &hotObject, &activeObject, &clickedObject, &doubleClickedObject](ObjectId id, const ImRect& rect, Object* object) + { + if (emitInteractiveArea(id, rect) >= 0) + clickedObject = object; + if (!doubleClickedObject && ImGui::IsMouseDoubleClicked(m_Config.DragButtonIndex) && ImGui::IsItemHovered()) + doubleClickedObject = object; + + if (!hotObject && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + hotObject = object; + + if (ImGui::IsItemActive()) + activeObject = object; + }; + + // Process live nodes and pins. + for (auto nodeIt = m_Nodes.rbegin(), nodeItEnd = m_Nodes.rend(); nodeIt != nodeItEnd; ++nodeIt) + { + auto node = *nodeIt; + + if (!node->m_IsLive) continue; + + // Check for interactions with live pins in node before + // processing node itself. Pins does not overlap each other + // and all are within node bounds. + for (auto pin = node->m_LastPin; pin; pin = pin->m_PreviousPin) + { + if (!pin->m_IsLive) continue; + + checkInteractionsInArea(pin->m_ID, pin->m_Bounds, pin); + } + + // Check for interactions with node. + if (node->m_Type == NodeType::Group) + { + // Node with a hole + ImGui::PushID(node->m_ID.AsPointer()); + + static const NodeRegion c_Regions[] = + { + NodeRegion::TopLeft, + NodeRegion::TopRight, + NodeRegion::BottomLeft, + NodeRegion::BottomRight, + NodeRegion::Top, + NodeRegion::Bottom, + NodeRegion::Left, + NodeRegion::Right, + NodeRegion::Header, + }; + + for (auto region : c_Regions) + { + auto bounds = node->GetRegionBounds(region); + if (ImRect_IsEmpty(bounds)) + continue; + checkInteractionsInArea(NodeId(static_cast(region)), bounds, node); + } + + ImGui::PopID(); + } + else + checkInteractionsInArea(node->m_ID, node->m_Bounds, node); + } + + // Links are not regular widgets and must be done manually since + // ImGui does not support interactive elements with custom hit maps. + // + // Links can steal input from background. + + // Links are just over background. So if anything else + // is hovered we can skip them. + if (nullptr == hotObject) + hotObject = FindLinkAt(mousePos); + + ImGuiButtonFlags backgroundExtraFlags = ImGuiButtonFlags_None; + if (m_Config.DragButtonIndex == 0 || m_Config.SelectButtonIndex == 0 || m_Config.NavigateButtonIndex == 0) + backgroundExtraFlags |= ImGuiButtonFlags_MouseButtonLeft; + if (m_Config.DragButtonIndex == 1 || m_Config.SelectButtonIndex == 1 || m_Config.NavigateButtonIndex == 1) + backgroundExtraFlags |= ImGuiButtonFlags_MouseButtonRight; + if (m_Config.DragButtonIndex == 2 || m_Config.SelectButtonIndex == 2 || m_Config.NavigateButtonIndex == 2) + backgroundExtraFlags |= ImGuiButtonFlags_MouseButtonMiddle; + + auto isMouseDoubleClickOverBackground = [doubleClickedObject, backgroundExtraFlags]() -> int + { + if (doubleClickedObject) + return -1; + + if (!ImGui::IsItemHovered()) + return -1; + + if ((backgroundExtraFlags & ImGuiButtonFlags_MouseButtonLeft) && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + return ImGuiButtonFlags_MouseButtonLeft; + if ((backgroundExtraFlags & ImGuiButtonFlags_MouseButtonRight) && ImGui::IsMouseDoubleClicked(ImGuiButtonFlags_MouseButtonRight)) + return ImGuiButtonFlags_MouseButtonRight; + if ((backgroundExtraFlags & ImGuiButtonFlags_MouseButtonMiddle) && ImGui::IsMouseDoubleClicked(ImGuiButtonFlags_MouseButtonMiddle)) + return ImGuiButtonFlags_MouseButtonMiddle; + + return -1; + }; + + // Check for interaction with background. + auto backgroundClickButonIndex = emitInteractiveAreaEx(NodeId(0), editorRect, backgroundExtraFlags); + auto backgroundDoubleClickButtonIndex = isMouseDoubleClickOverBackground(); + auto isBackgroundActive = ImGui::IsItemActive(); + auto isBackgroundHot = !hotObject; + auto isDragging = ImGui::IsMouseDragging(0, 1) || ImGui::IsMouseDragging(1, 1) || ImGui::IsMouseDragging(2, 1); + + if (backgroundDoubleClickButtonIndex >= 0) + backgroundClickButonIndex = -1; + + if (isMouseOffscreen) + ImGui::PopClipRect(); + + // Process link input using background interactions. + auto hotLink = hotObject ? hotObject->AsLink() : nullptr; + + // ImGui take care of tracking active items. With link + // we must do this ourself. + if (!isDragging && isBackgroundActive && hotLink && !m_LastActiveLink) + m_LastActiveLink = hotLink; + if (isBackgroundActive && m_LastActiveLink) + { + activeObject = m_LastActiveLink; + isBackgroundActive = false; + } + else if (!isBackgroundActive && m_LastActiveLink) + m_LastActiveLink = nullptr; + + // Steal click from backgrounds if link is hovered. + if (!isDragging && backgroundClickButonIndex >= 0 && hotLink) + { + clickedObject = hotLink; + backgroundClickButonIndex = -1; + } + + // Steal double-click from backgrounds if link is hovered. + if (!isDragging && backgroundDoubleClickButtonIndex >= 0 && hotLink) + { + doubleClickedObject = hotLink; + backgroundDoubleClickButtonIndex = -1; + } + + if (activeId) + m_EditorActiveId = activeId; + + if (ImGui::IsAnyItemActive() && ImGui::GetActiveID() != m_EditorActiveId) + return Control(); + + m_IsHovered = ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly); + m_IsHoveredWithoutOverlapp = ImGui::IsItemHovered(); + if (!allowOffscreen && !m_IsHovered) + return Control(); + +# if IMGUI_VERSION_NUM >= 18836 + if (m_IsHoveredWithoutOverlapp) + ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY); +# elif IMGUI_VERSION_NUM >= 17909 + if (m_IsHoveredWithoutOverlapp) + ImGui::SetItemUsingMouseWheel(); +# endif + + return Control(hotObject, activeObject, clickedObject, doubleClickedObject, + isBackgroundHot, isBackgroundActive, backgroundClickButonIndex, backgroundDoubleClickButtonIndex); +} + +void ed::EditorContext::ShowMetrics(const Control& control) +{ + auto& io = ImGui::GetIO(); + + auto getObjectName = [](Object* object) + { + if (!object) return ""; + else if (object->AsNode()) return "Node"; + else if (object->AsPin()) return "Pin"; + else if (object->AsLink()) return "Link"; + else return ""; + }; + + auto getHotObjectName = [&control, &getObjectName]() + { + if (control.HotObject) + return getObjectName(control.HotObject); + else if (control.BackgroundHot) + return "Background"; + else + return ""; + }; + + auto getActiveObjectName = [&control, &getObjectName]() + { + if (control.ActiveObject) + return getObjectName(control.ActiveObject); + else if (control.BackgroundActive) + return "Background"; + else + return ""; + }; + + auto liveNodeCount = CountLiveNodes(); + auto livePinCount = CountLivePins(); + auto liveLinkCount = CountLiveLinks(); + + auto canvasRect = m_Canvas.Rect(); + auto viewRect = m_Canvas.ViewRect(); + auto localMousePos = m_Canvas.ToLocal(io.MousePos); + auto globalMousePos = io.MousePos; + + ImGui::SetCursorScreenPos(canvasRect.Min + ImVec2(5, 5)); + ImGui::BeginGroup(); + ImGui::Text("Is Focused: %s", m_IsFocused ? "true" : "false"); + ImGui::Text("Is Hovered: %s", m_IsHovered ? "true" : "false"); + ImGui::Text("Is Hovered (without overlapp): %s", m_IsHoveredWithoutOverlapp ? "true" : "false"); + ImGui::Text("Accept Input: %s", CanAcceptUserInput() ? "true" : "false"); + ImGui::Text("View Position: { x=%g y=%g }", viewRect.Min.x, viewRect.Min.y); + ImGui::Text("View Size: { w=%g h=%g }", viewRect.GetWidth(), viewRect.GetHeight()); + ImGui::Text("Canvas Size: { w=%g h=%g }", canvasRect.GetWidth(), canvasRect.GetHeight()); + ImGui::Text("Mouse: { x=%.0f y=%.0f } global: { x=%g y=%g }", localMousePos.x, localMousePos.y, globalMousePos.x, globalMousePos.y); + ImGui::Text("Live Nodes: %d", liveNodeCount); + ImGui::Text("Live Pins: %d", livePinCount); + ImGui::Text("Live Links: %d", liveLinkCount); + ImGui::Text("Hot Object: %s (%p)", getHotObjectName(), control.HotObject ? control.HotObject->ID().AsPointer() : nullptr); + if (auto node = control.HotObject ? control.HotObject->AsNode() : nullptr) + { + ImGui::SameLine(); + ImGui::Text("{ x=%g y=%g w=%g h=%g }", node->m_Bounds.Min.x, node->m_Bounds.Min.y, node->m_Bounds.GetWidth(), node->m_Bounds.GetHeight()); + } + ImGui::Text("Active Object: %s (%p)", getActiveObjectName(), control.ActiveObject ? control.ActiveObject->ID().AsPointer() : nullptr); + if (auto node = control.ActiveObject ? control.ActiveObject->AsNode() : nullptr) + { + ImGui::SameLine(); + ImGui::Text("{ x=%g y=%g w=%g h=%g }", node->m_Bounds.Min.x, node->m_Bounds.Min.y, node->m_Bounds.GetWidth(), node->m_Bounds.GetHeight()); + } + ImGui::Text("Action: %s", m_CurrentAction ? m_CurrentAction->GetName() : ""); + ImGui::Text("Action Is Dragging: %s", m_CurrentAction && m_CurrentAction->IsDragging() ? "Yes" : "No"); + m_NavigateAction.ShowMetrics(); + m_SizeAction.ShowMetrics(); + m_DragAction.ShowMetrics(); + m_SelectAction.ShowMetrics(); + m_ContextMenuAction.ShowMetrics(); + m_CreateItemAction.ShowMetrics(); + m_DeleteItemsAction.ShowMetrics(); + ImGui::EndGroup(); +} + + + + +//------------------------------------------------------------------------------ +// +// Node Settings +// +//------------------------------------------------------------------------------ +void ed::NodeSettings::ClearDirty() +{ + m_IsDirty = false; + m_DirtyReason = SaveReasonFlags::None; +} + +void ed::NodeSettings::MakeDirty(SaveReasonFlags reason) +{ + m_IsDirty = true; + m_DirtyReason = m_DirtyReason | reason; +} + +ed::json::value ed::NodeSettings::Serialize() +{ + json::value result; + result["location"]["x"] = m_Location.x; + result["location"]["y"] = m_Location.y; + + if (m_GroupSize.x > 0 || m_GroupSize.y > 0) + { + result["group_size"]["x"] = m_GroupSize.x; + result["group_size"]["y"] = m_GroupSize.y; + } + + return result; +} + +bool ed::NodeSettings::Parse(const std::string& string, NodeSettings& settings) +{ + auto settingsValue = json::value::parse(string); + if (settingsValue.is_discarded()) + return false; + + return Parse(settingsValue, settings); +} + +bool ed::NodeSettings::Parse(const json::value& data, NodeSettings& result) +{ + if (!data.is_object()) + return false; + + auto tryParseVector = [](const json::value& v, ImVec2& result) -> bool + { + if (v.is_object()) + { + auto xValue = v["x"]; + auto yValue = v["y"]; + + if (xValue.is_number() && yValue.is_number()) + { + result.x = static_cast(xValue.get()); + result.y = static_cast(yValue.get()); + + return true; + } + } + + return false; + }; + + if (!tryParseVector(data["location"], result.m_Location)) + return false; + + if (data.contains("group_size") && !tryParseVector(data["group_size"], result.m_GroupSize)) + return false; + + return true; +} + + + + +//------------------------------------------------------------------------------ +// +// Settings +// +//------------------------------------------------------------------------------ +ed::NodeSettings* ed::Settings::AddNode(NodeId id) +{ + m_Nodes.push_back(NodeSettings(id)); + return &m_Nodes.back(); +} + +ed::NodeSettings* ed::Settings::FindNode(NodeId id) +{ + for (auto& settings : m_Nodes) + if (settings.m_ID == id) + return &settings; + + return nullptr; +} + +void ed::Settings::RemoveNode(NodeId id) +{ + auto node = FindNode(id); + if (!node) + return; + + *node = NodeSettings(id); +} + +void ed::Settings::ClearDirty(Node* node) +{ + if (node) + { + auto settings = FindNode(node->m_ID); + IM_ASSERT(settings); + settings->ClearDirty(); + } + else + { + m_IsDirty = false; + m_DirtyReason = SaveReasonFlags::None; + + for (auto& knownNode : m_Nodes) + knownNode.ClearDirty(); + } +} + +void ed::Settings::MakeDirty(SaveReasonFlags reason, Node* node) +{ + m_IsDirty = true; + m_DirtyReason = m_DirtyReason | reason; + + if (node) + { + auto settings = FindNode(node->m_ID); + IM_ASSERT(settings); + + settings->MakeDirty(reason); + } +} + +std::string ed::Settings::Serialize() +{ + json::value result; + + auto serializeObjectId = [](ObjectId id) + { + auto value = std::to_string(reinterpret_cast(id.AsPointer())); + switch (id.Type()) + { + default: + case NodeEditor::Detail::ObjectType::None: return value; + case NodeEditor::Detail::ObjectType::Node: return "node:" + value; + case NodeEditor::Detail::ObjectType::Link: return "link:" + value; + case NodeEditor::Detail::ObjectType::Pin: return "pin:" + value; + } + }; + + auto& nodes = result["nodes"]; + for (auto& node : m_Nodes) + { + if (node.m_WasUsed) + nodes[serializeObjectId(node.m_ID)] = node.Serialize(); + } + + auto& selection = result["selection"]; + for (auto& id : m_Selection) + selection.push_back(serializeObjectId(id)); + + auto& view = result["view"]; + view["scroll"]["x"] = m_ViewScroll.x; + view["scroll"]["y"] = m_ViewScroll.y; + view["zoom"] = m_ViewZoom; + view["visible_rect"]["min"]["x"] = m_VisibleRect.Min.x; + view["visible_rect"]["min"]["y"] = m_VisibleRect.Min.y; + view["visible_rect"]["max"]["x"] = m_VisibleRect.Max.x; + view["visible_rect"]["max"]["y"] = m_VisibleRect.Max.y; + + return result.dump(); +} + +bool ed::Settings::Parse(const std::string& string, Settings& settings) +{ + Settings result = settings; + + auto settingsValue = json::value::parse(string); + if (settingsValue.is_discarded()) + return false; + + if (!settingsValue.is_object()) + return false; + + auto tryParseVector = [](const json::value& v, ImVec2& result) -> bool + { + if (v.is_object() && v.contains("x") && v.contains("y")) + { + auto xValue = v["x"]; + auto yValue = v["y"]; + + if (xValue.is_number() && yValue.is_number()) + { + result.x = static_cast(xValue.get()); + result.y = static_cast(yValue.get()); + + return true; + } + } + + return false; + }; + + auto deserializeObjectId = [](const std::string& str) + { + auto separator = str.find_first_of(':'); + auto idStart = str.c_str() + ((separator != std::string::npos) ? separator + 1 : 0); + auto id = reinterpret_cast(strtoull(idStart, nullptr, 10)); + if (str.compare(0, separator, "node") == 0) + return ObjectId(NodeId(id)); + else if (str.compare(0, separator, "link") == 0) + return ObjectId(LinkId(id)); + else if (str.compare(0, separator, "pin") == 0) + return ObjectId(PinId(id)); + else + // fallback to old format + return ObjectId(NodeId(id)); //return ObjectId(); + }; + + //auto& settingsObject = settingsValue.get(); + + auto& nodesValue = settingsValue["nodes"]; + if (nodesValue.is_object()) + { + for (auto& node : nodesValue.get()) + { + auto id = deserializeObjectId(node.first.c_str()).AsNodeId(); + + auto nodeSettings = result.FindNode(id); + if (!nodeSettings) + nodeSettings = result.AddNode(id); + + NodeSettings::Parse(node.second, *nodeSettings); + } + } + + auto& selectionValue = settingsValue["selection"]; + if (selectionValue.is_array()) + { + const auto selectionArray = selectionValue.get(); + + result.m_Selection.reserve(selectionArray.size()); + result.m_Selection.resize(0); + for (auto& selection : selectionArray) + { + if (selection.is_string()) + result.m_Selection.push_back(deserializeObjectId(selection.get())); + } + } + + auto& viewValue = settingsValue["view"]; + if (viewValue.is_object()) + { + auto& viewScrollValue = viewValue["scroll"]; + auto& viewZoomValue = viewValue["zoom"]; + + if (!tryParseVector(viewScrollValue, result.m_ViewScroll)) + result.m_ViewScroll = ImVec2(0, 0); + + result.m_ViewZoom = viewZoomValue.is_number() ? static_cast(viewZoomValue.get()) : 1.0f; + + if (!viewValue.contains("visible_rect") || !tryParseVector(viewValue["visible_rect"]["min"], result.m_VisibleRect.Min) || !tryParseVector(viewValue["visible_rect"]["max"], result.m_VisibleRect.Max)) + result.m_VisibleRect = {}; + } + + settings = std::move(result); + + return true; +} + + + +//------------------------------------------------------------------------------ +// +// Animation +// +//------------------------------------------------------------------------------ +ed::Animation::Animation(EditorContext* editor): + Editor(editor), + m_State(Stopped), + m_Time(0.0f), + m_Duration(0.0f) +{ +} + +ed::Animation::~Animation() +{ + Stop(); +} + +void ed::Animation::Play(float duration) +{ + if (IsPlaying()) + Stop(); + + m_State = Playing; + if (duration < 0) + duration = 0.0f; + + m_Time = 0.0f; + m_Duration = duration; + + OnPlay(); + + Editor->RegisterAnimation(this); + + if (duration == 0.0f) + Finish(); +} + +void ed::Animation::Stop() +{ + if (!IsPlaying()) + return; + + m_State = Stopped; + + Editor->UnregisterAnimation(this); + + OnStop(); +} + +void ed::Animation::Finish() +{ + if (!IsPlaying()) + return; + + OnFinish(); + + Stop(); +} + +void ed::Animation::Update() +{ + if (!IsPlaying()) + return; + + m_Time += ImMax(0.0f, ImGui::GetIO().DeltaTime); + if (m_Time < m_Duration) + { + const float progress = GetProgress(); + OnUpdate(progress); + } + else + { + OnFinish(); + Stop(); + } +} + + + + +//------------------------------------------------------------------------------ +// +// Navigate Animation +// +//------------------------------------------------------------------------------ +ed::NavigateAnimation::NavigateAnimation(EditorContext* editor, NavigateAction& scrollAction): + Animation(editor), + Action(scrollAction) +{ +} + +void ed::NavigateAnimation::NavigateTo(const ImRect& target, float duration) +{ + Stop(); + + m_Start = Action.GetViewRect(); + m_Target = target; + + // Skip tiny animations + auto minoffset = m_Target.Min - m_Start.Min; + auto maxOffset = m_Target.Max - m_Start.Max; + auto epsilon = 1e-4f; + if (ImFabs(minoffset.x) < epsilon && ImFabs(minoffset.y) < epsilon && + ImFabs(maxOffset.x) < epsilon && ImFabs(maxOffset.y) < epsilon) + { + duration = 0; + } + + Play(duration); +} + +void ed::NavigateAnimation::OnUpdate(float progress) +{ + ImRect current; + current.Min = ImEasing::EaseOutQuad(m_Start.Min, m_Target.Min - m_Start.Min, progress); + current.Max = ImEasing::EaseOutQuad(m_Start.Max, m_Target.Max - m_Start.Max, progress); + Action.SetViewRect(current); +} + +void ed::NavigateAnimation::OnStop() +{ + Editor->MakeDirty(SaveReasonFlags::Navigation); +} + +void ed::NavigateAnimation::OnFinish() +{ + Action.SetViewRect(m_Target); + + Editor->MakeDirty(SaveReasonFlags::Navigation); +} + + + + +//------------------------------------------------------------------------------ +// +// Flow Animation +// +//------------------------------------------------------------------------------ +ed::FlowAnimation::FlowAnimation(FlowAnimationController* controller): + Animation(controller->Editor), + Controller(controller), + m_Link(nullptr), + m_Offset(0.0f), + m_PathLength(0.0f) +{ +} + +void ed::FlowAnimation::Flow(ed::Link* link, float markerDistance, float speed, float duration) +{ + Stop(); + + if (m_Link != link) + { + m_Offset = 0.0f; + ClearPath(); + } + + if (m_MarkerDistance != markerDistance) + ClearPath(); + + m_MarkerDistance = markerDistance; + m_Speed = speed; + m_Link = link; + + Play(duration); +} + +void ed::FlowAnimation::Draw(ImDrawList* drawList) +{ + if (!IsPlaying() || !IsLinkValid() || !m_Link->IsVisible()) + return; + + if (!IsPathValid()) + UpdatePath(); + + m_Offset = fmodf(m_Offset, m_MarkerDistance); + if (m_Offset < 0) + m_Offset += m_MarkerDistance; + + const auto progress = GetProgress(); + + const auto flowAlpha = 1.0f - progress * progress; + const auto flowColor = Editor->GetColor(StyleColor_Flow, flowAlpha); + //const auto flowPath = Link->GetCurve(); + + m_Link->Draw(drawList, flowColor, 2.0f); + + if (IsPathValid()) + { + //Offset = 0; + + const auto markerAlpha = powf(1.0f - progress, 0.35f); + const auto markerRadius = 4.0f * (1.0f - progress) + 2.0f; + const auto markerColor = Editor->GetColor(StyleColor_FlowMarker, markerAlpha); + + for (float d = m_Offset; d < m_PathLength; d += m_MarkerDistance) + drawList->AddCircleFilled(SamplePath(d), markerRadius, markerColor); + } +} + +bool ed::FlowAnimation::IsLinkValid() const +{ + return m_Link && m_Link->m_IsLive; +} + +bool ed::FlowAnimation::IsPathValid() const +{ + return m_Path.size() > 1 && m_PathLength > 0.0f && m_Link->m_Start == m_LastStart && m_Link->m_End == m_LastEnd; +} + +void ed::FlowAnimation::UpdatePath() +{ + if (!IsLinkValid()) + { + ClearPath(); + return; + } + + const auto curve = m_Link->GetCurve(); + + m_LastStart = m_Link->m_Start; + m_LastEnd = m_Link->m_End; + m_PathLength = ImCubicBezierLength(curve.P0, curve.P1, curve.P2, curve.P3); + + auto collectPointsCallback = [this](ImCubicBezierFixedStepSample& result) + { + m_Path.push_back(CurvePoint{ result.Length, result.Point }); + }; + + const auto step = ImMax(m_MarkerDistance * 0.5f, 15.0f); + + m_Path.resize(0); + ImCubicBezierFixedStep(collectPointsCallback, curve, step, false, 0.5f, 0.001f); +} + +void ed::FlowAnimation::ClearPath() +{ + vector().swap(m_Path); + m_PathLength = 0.0f; +} + +ImVec2 ed::FlowAnimation::SamplePath(float distance) const +{ + //distance = ImMax(0.0f, std::min(distance, PathLength)); + + auto endPointIt = std::find_if(m_Path.begin(), m_Path.end(), [distance](const CurvePoint& p) { return distance < p.Distance; }); + if (endPointIt == m_Path.end()) + endPointIt = m_Path.end() - 1; + else if (endPointIt == m_Path.begin()) + endPointIt = m_Path.begin() + 1; + + const auto& start = endPointIt[-1]; + const auto& end = *endPointIt; + const auto t = (distance - start.Distance) / (end.Distance - start.Distance); + + return start.Point + (end.Point - start.Point) * t; +} + +void ed::FlowAnimation::OnUpdate(float progress) +{ + IM_UNUSED(progress); + + m_Offset += m_Speed * ImGui::GetIO().DeltaTime; +} + +void ed::FlowAnimation::OnStop() +{ + Controller->Release(this); +} + + + + +//------------------------------------------------------------------------------ +// +// Flow Animation Controller +// +//------------------------------------------------------------------------------ +ed::FlowAnimationController::FlowAnimationController(EditorContext* editor): + AnimationController(editor) +{ +} + +ed::FlowAnimationController::~FlowAnimationController() +{ + for (auto animation : m_Animations) + delete animation; +} + +void ed::FlowAnimationController::Flow(Link* link, FlowDirection direction) +{ + if (!link || !link->m_IsLive) + return; + + auto& editorStyle = GetStyle(); + + auto animation = GetOrCreate(link); + + float speedDirection = 1.0f; + if (direction == FlowDirection::Backward) + speedDirection = -1.0f; + + animation->Flow(link, editorStyle.FlowMarkerDistance, editorStyle.FlowSpeed * speedDirection, editorStyle.FlowDuration); +} + +void ed::FlowAnimationController::Draw(ImDrawList* drawList) +{ + if (m_Animations.empty()) + return; + + drawList->ChannelsSetCurrent(c_LinkChannel_Flow); + + for (auto animation : m_Animations) + animation->Draw(drawList); +} + +ed::FlowAnimation* ed::FlowAnimationController::GetOrCreate(Link* link) +{ + // Return live animation which match target link + { + auto animationIt = std::find_if(m_Animations.begin(), m_Animations.end(), [link](FlowAnimation* animation) { return animation->m_Link == link; }); + if (animationIt != m_Animations.end()) + return *animationIt; + } + + // There are no live animations for target link, try to reuse inactive old one + if (!m_FreePool.empty()) + { + auto animation = m_FreePool.back(); + m_FreePool.pop_back(); + return animation; + } + + // Cache miss, allocate new one + auto animation = new FlowAnimation(this); + m_Animations.push_back(animation); + + return animation; +} + +void ed::FlowAnimationController::Release(FlowAnimation* animation) +{ + IM_UNUSED(animation); +} + + + +//------------------------------------------------------------------------------ +// +// Navigate Action +// +//------------------------------------------------------------------------------ +const float ed::NavigateAction::s_DefaultZoomLevels[] = +{ + 0.1f, 0.15f, 0.20f, 0.25f, 0.33f, 0.5f, 0.75f, 1.0f, 1.25f, 1.50f, 2.0f, 2.5f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f +}; + +const int ed::NavigateAction::s_DefaultZoomLevelCount = sizeof(s_DefaultZoomLevels) / sizeof(*s_DefaultZoomLevels); + +ed::NavigateAction::NavigateAction(EditorContext* editor, ImGuiEx::Canvas& canvas): + EditorAction(editor), + m_IsActive(false), + m_Zoom(1), + m_VisibleRect(), + m_Scroll(0, 0), + m_ScrollStart(0, 0), + m_ScrollDelta(0, 0), + m_Canvas(canvas), + m_WindowScreenPos(0, 0), + m_WindowScreenSize(0, 0), + m_Animation(editor, *this), + m_Reason(NavigationReason::Unknown), + m_LastSelectionId(0), + m_LastObject(nullptr), + m_MovingOverEdge(false), + m_MoveScreenOffset(0, 0), + m_ZoomLevels(editor->GetConfig().CustomZoomLevels.Size > 0 ? editor->GetConfig().CustomZoomLevels.Data : s_DefaultZoomLevels), + m_ZoomLevelCount(editor->GetConfig().CustomZoomLevels.Size > 0 ? editor->GetConfig().CustomZoomLevels.Size : s_DefaultZoomLevelCount) +{ +} + +ed::EditorAction::AcceptResult ed::NavigateAction::Accept(const Control& control) +{ + IM_ASSERT(!m_IsActive); + + if (m_IsActive) + return False; + + if (Editor->CanAcceptUserInput() /*&& !ImGui::IsAnyItemActive()*/ && ImGui::IsMouseDragging(Editor->GetConfig().NavigateButtonIndex, 0.0f)) + { + m_IsActive = true; + m_ScrollStart = m_Scroll; + m_ScrollDelta = ImGui::GetMouseDragDelta(Editor->GetConfig().NavigateButtonIndex); + m_Scroll = m_ScrollStart - m_ScrollDelta * m_Zoom; + } + + auto& io = ImGui::GetIO(); + + if (Editor->CanAcceptUserInput() && ImGui::IsKeyPressed(GetKeyIndexForF()) && Editor->AreShortcutsEnabled()) + { + const auto zoomMode = io.KeyShift ? NavigateAction::ZoomMode::WithMargin : NavigateAction::ZoomMode::None; + + auto findHotObjectToZoom = [this, &control, &io]() -> Object* + { + if (control.HotObject) + { + if (auto pin = control.HotObject->AsPin()) + return pin->m_Node; + else + return control.HotObject; + } + else if (control.BackgroundHot) + { + auto node = Editor->FindNodeAt(io.MousePos); + if (IsGroup(node)) + return node; + } + + return nullptr; + }; + + bool navigateToContent = false; + if (!Editor->GetSelectedObjects().empty()) + { + if (m_Reason != NavigationReason::Selection || m_LastSelectionId != Editor->GetSelectionId() || (zoomMode != NavigateAction::ZoomMode::None)) + { + m_LastSelectionId = Editor->GetSelectionId(); + NavigateTo(Editor->GetSelectionBounds(), zoomMode, -1.0f, NavigationReason::Selection); + } + else + navigateToContent = true; + } + else if(auto hotObject = findHotObjectToZoom()) + { + if (m_Reason != NavigationReason::Object || m_LastObject != hotObject || (zoomMode != NavigateAction::ZoomMode::None)) + { + m_LastObject = hotObject; + auto bounds = hotObject->GetBounds(); + NavigateTo(bounds, zoomMode, -1.0f, NavigationReason::Object); + } + else + navigateToContent = true; + } + else + navigateToContent = true; + + if (navigateToContent) + NavigateTo(Editor->GetContentBounds(), NavigateAction::ZoomMode::WithMargin, -1.0f, NavigationReason::Content); + } + + auto visibleRect = GetViewRect(); + if (m_VisibleRect.Min != visibleRect.Min || m_VisibleRect.Max != visibleRect.Max) + { + m_VisibleRect = visibleRect; + Editor->MakeDirty(SaveReasonFlags::Navigation); + } + + // // #debug + // if (m_DrawList) + // m_DrawList->AddCircleFilled(io.MousePos, 4.0f, IM_COL32(255, 0, 255, 255)); + + if (HandleZoom(control)) + return True; + + return m_IsActive ? True : False; +} + +bool ed::NavigateAction::Process(const Control& control) +{ + IM_UNUSED(control); + + if (!m_IsActive) + return false; + + if (ImGui::IsMouseDragging(Editor->GetConfig().NavigateButtonIndex, 0.0f)) + { + m_ScrollDelta = ImGui::GetMouseDragDelta(Editor->GetConfig().NavigateButtonIndex); + m_Scroll = m_ScrollStart - m_ScrollDelta * m_Zoom; + m_VisibleRect = GetViewRect(); +// if (IsActive && Animation.IsPlaying()) +// Animation.Target = Animation.Target - ScrollDelta * Animation.TargetZoom; + } + else + { + if (m_Scroll != m_ScrollStart) + Editor->MakeDirty(SaveReasonFlags::Navigation); + + m_IsActive = false; + } + + // #TODO: Handle zoom while scrolling + // HandleZoom(control); + + return m_IsActive; +} + +bool ed::NavigateAction::HandleZoom(const Control& control) +{ + IM_UNUSED(control); + + const auto currentAction = Editor->GetCurrentAction(); + const auto allowOffscreen = currentAction && currentAction->IsDragging(); + + auto& io = ImGui::GetIO(); + + if (!io.MouseWheel || (!allowOffscreen && !Editor->IsHoveredWithoutOverlapp()))// && !ImGui::IsAnyItemActive()) + return false; + + auto savedScroll = m_Scroll; + auto savedZoom = m_Zoom; + + m_Animation.Finish(); + + auto mousePos = io.MousePos; + auto newZoom = GetNextZoom(io.MouseWheel); + + auto oldView = GetView(); + m_Zoom = newZoom; + auto newView = GetView(); + + auto screenPos = m_Canvas.FromLocal(mousePos, oldView); + auto canvasPos = m_Canvas.ToLocal(screenPos, newView); + + auto offset = (canvasPos - mousePos) * m_Zoom; + auto targetScroll = m_Scroll - offset; + + auto visibleRect = GetViewRect(); + + if (m_Scroll != savedScroll || m_Zoom != savedZoom || m_VisibleRect.Min != visibleRect.Min || m_VisibleRect.Max != visibleRect.Max) + { + m_Scroll = savedScroll; + m_Zoom = savedZoom; + m_VisibleRect = visibleRect; + + Editor->MakeDirty(SaveReasonFlags::Navigation); + } + + auto targetRect = m_Canvas.CalcViewRect(ImGuiEx::CanvasView(-targetScroll, newZoom)); + + NavigateTo(targetRect, c_MouseZoomDuration, NavigationReason::MouseZoom); + + return true; +} + +void ed::NavigateAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); + ImGui::Text(" Scroll: { x=%g y=%g }", m_Scroll.x, m_Scroll.y); + ImGui::Text(" Zoom: %g", m_Zoom); + ImGui::Text(" Visible Rect: { l=%g t=%g, r=%g b=%g w=%g h=%g }", + m_VisibleRect.Min.x, m_VisibleRect.Min.y, + m_VisibleRect.Max.x, m_VisibleRect.Max.y, + m_VisibleRect.Max.x - m_VisibleRect.Min.x, + m_VisibleRect.Max.y - m_VisibleRect.Min.y); +} + +void ed::NavigateAction::NavigateTo(const ImRect& bounds, ZoomMode zoomMode, float duration, NavigationReason reason) +{ + if (ImRect_IsEmpty(bounds)) + return; + + if (duration < 0.0f) + duration = GetStyle().ScrollDuration; + + if (zoomMode == ZoomMode::None) + { + auto viewRect = m_Canvas.ViewRect(); + auto viewRectCenter = viewRect.GetCenter(); + auto targetCenter = bounds.GetCenter(); + + viewRect.Translate(targetCenter - viewRectCenter); + + NavigateTo(viewRect, duration, reason); + } + else + { + // Grow rect by 5% to leave some reasonable margin + // from the edges of the canvas. + auto rect = bounds; + + if (zoomMode == ZoomMode::WithMargin) + { + auto extend = ImMax(rect.GetWidth(), rect.GetHeight()); + rect.Expand(extend * c_NavigationZoomMargin * 0.5f); + } + + NavigateTo(rect, duration, reason); + } +} + +void ed::NavigateAction::NavigateTo(const ImRect& target, float duration, NavigationReason reason) +{ + m_Reason = reason; + + m_Animation.NavigateTo(target, duration); +} + +void ed::NavigateAction::StopNavigation() +{ + m_Animation.Stop(); +} + +void ed::NavigateAction::FinishNavigation() +{ + m_Animation.Finish(); +} + +bool ed::NavigateAction::MoveOverEdge(const ImVec2& canvasSize) +{ + // Don't interrupt non-edge animations + if (m_Animation.IsPlaying()) + return false; + + auto& io = ImGui::GetIO(); + + const auto screenMousePos = io.MousePos; + const auto screenRect = ImRect(ImGui::GetCursorScreenPos(), ImGui::GetCursorScreenPos() + canvasSize); + + // Mouse is over screen, do nothing + if (screenRect.Contains(screenMousePos)) + return false; + + // Several backend move mouse position to -FLT_MAX to indicate + // uninitialized/unknown state. To prevent all sorts + // of math problems, we just ignore such state. + if (screenMousePos.x <= -FLT_MAX || screenMousePos.y <= -FLT_MAX) + return false; + + const auto minDistance = ImVec2(-c_MaxMoveOverEdgeDistance, -c_MaxMoveOverEdgeDistance); + const auto maxDistance = ImVec2( c_MaxMoveOverEdgeDistance, c_MaxMoveOverEdgeDistance); + + const auto screenPointOnEdge = ImRect_ClosestPoint(screenRect, screenMousePos, true); + const auto offset = ImMin(ImMax(screenPointOnEdge - screenMousePos, minDistance), maxDistance); + const auto relativeOffset = -offset * io.DeltaTime * c_MaxMoveOverEdgeSpeed; + + m_Scroll = m_Scroll + relativeOffset; + + m_MoveScreenOffset = relativeOffset; + m_MovingOverEdge = true; + + return true; +} + +void ed::NavigateAction::StopMoveOverEdge() +{ + if (m_MovingOverEdge) + { + Editor->MakeDirty(SaveReasonFlags::Navigation); + + m_MoveScreenOffset = ImVec2(0, 0); + m_MovingOverEdge = false; + } +} + +void ed::NavigateAction::SetWindow(ImVec2 position, ImVec2 size) +{ + m_WindowScreenPos = position; + m_WindowScreenSize = size; +} + +ImGuiEx::CanvasView ed::NavigateAction::GetView() const +{ + return ImGuiEx::CanvasView(-m_Scroll, m_Zoom); +} + +ImVec2 ed::NavigateAction::GetViewOrigin() const +{ + return -m_Scroll; +} + +float ed::NavigateAction::GetViewScale() const +{ + return m_Zoom; +} + +void ed::NavigateAction::SetViewRect(const ImRect& rect) +{ + auto view = m_Canvas.CalcCenterView(rect); + m_Scroll = -view.Origin; + m_Zoom = view.Scale; +} + +ImRect ed::NavigateAction::GetViewRect() const +{ + return m_Canvas.CalcViewRect(GetView()); +} + +float ed::NavigateAction::GetNextZoom(float steps) +{ + if (this->Editor->GetConfig().EnableSmoothZoom) + { + return MatchSmoothZoom(steps); + } + else + { + auto fixedSteps = (int)steps; + return MatchZoom(fixedSteps, m_ZoomLevels[fixedSteps < 0 ? 0 : m_ZoomLevelCount - 1]); + } +} + +float ed::NavigateAction::MatchSmoothZoom(float steps) +{ + const auto power = Editor->GetConfig().SmoothZoomPower; + + const auto newZoom = m_Zoom * powf(power, steps); + if (newZoom < m_ZoomLevels[0]) + return m_ZoomLevels[0]; + else if (newZoom > m_ZoomLevels[m_ZoomLevelCount - 1]) + return m_ZoomLevels[m_ZoomLevelCount - 1]; + else + return newZoom; +} + +float ed::NavigateAction::MatchZoom(int steps, float fallbackZoom) +{ + auto currentZoomIndex = MatchZoomIndex(steps); + if (currentZoomIndex < 0) + return fallbackZoom; + + auto currentZoom = m_ZoomLevels[currentZoomIndex]; + if (fabsf(currentZoom - m_Zoom) > 0.001f) + return currentZoom; + + auto newIndex = currentZoomIndex + steps; + if (newIndex >= 0 && newIndex < m_ZoomLevelCount) + return m_ZoomLevels[newIndex]; + else + return fallbackZoom; +} + +int ed::NavigateAction::MatchZoomIndex(int direction) +{ + int bestIndex = -1; + float bestDistance = 0.0f; + + for (int i = 0; i < m_ZoomLevelCount; ++i) + { + auto distance = fabsf(m_ZoomLevels[i] - m_Zoom); + if (distance < bestDistance || bestIndex < 0) + { + bestDistance = distance; + bestIndex = i; + } + } + + if (bestDistance > 0.001f) + { + if (direction > 0) + { + ++bestIndex; + + if (bestIndex >= m_ZoomLevelCount) + bestIndex = m_ZoomLevelCount - 1; + } + else if (direction < 0) + { + --bestIndex; + + if (bestIndex < 0) + bestIndex = 0; + } + } + + return bestIndex; +} + + + + +//------------------------------------------------------------------------------ +// +// Size Action +// +//------------------------------------------------------------------------------ +ed::SizeAction::SizeAction(EditorContext* editor): + EditorAction(editor), + m_IsActive(false), + m_Clean(false), + m_SizedNode(nullptr), + m_Pivot(NodeRegion::None), + m_Cursor(ImGuiMouseCursor_Arrow) +{ +} + +ed::EditorAction::AcceptResult ed::SizeAction::Accept(const Control& control) +{ + IM_ASSERT(!m_IsActive); + + if (m_IsActive) + return False; + + if (control.ActiveNode && IsGroup(control.ActiveNode) && ImGui::IsMouseDragging(Editor->GetConfig().DragButtonIndex, 1)) + { + //const auto mousePos = to_point(ImGui::GetMousePos()); + //const auto closestPoint = control.ActiveNode->Bounds.get_closest_point_hollow(mousePos, static_cast(control.ActiveNode->Rounding)); + + auto pivot = GetRegion(control.ActiveNode); + if (pivot != NodeRegion::Header && pivot != NodeRegion::Center) + { + m_StartBounds = control.ActiveNode->m_Bounds; + m_StartGroupBounds = control.ActiveNode->m_GroupBounds; + m_LastSize = control.ActiveNode->m_Bounds.GetSize(); + m_MinimumSize = ImVec2(0, 0); + m_LastDragOffset = ImVec2(0, 0); + m_Pivot = pivot; + m_Cursor = ChooseCursor(m_Pivot); + m_SizedNode = control.ActiveNode; + m_IsActive = true; + } + } + else if (control.HotNode && IsGroup(control.HotNode)) + { + m_Cursor = ChooseCursor(GetRegion(control.HotNode)); + return Possible; + } + + return m_IsActive ? True : False; +} + +bool ed::SizeAction::Process(const Control& control) +{ + if (m_Clean) + { + m_Clean = false; + + if (m_SizedNode->m_Bounds.Min != m_StartBounds.Min || m_SizedNode->m_GroupBounds.Min != m_StartGroupBounds.Min) + Editor->MakeDirty(SaveReasonFlags::Position | SaveReasonFlags::User, m_SizedNode); + + if (m_SizedNode->m_Bounds.GetSize() != m_StartBounds.GetSize() || m_SizedNode->m_GroupBounds.GetSize() != m_StartGroupBounds.GetSize()) + Editor->MakeDirty(SaveReasonFlags::Size | SaveReasonFlags::User, m_SizedNode); + + m_SizedNode = nullptr; + } + + if (!m_IsActive) + return false; + + if (control.ActiveNode == m_SizedNode) + { + const auto dragOffset = (control.ActiveNode == m_SizedNode) ? ImGui::GetMouseDragDelta(0, 0.0f) : m_LastDragOffset; + m_LastDragOffset = dragOffset; + + if (m_MinimumSize.x == 0.0f && m_LastSize.x != m_SizedNode->m_Bounds.GetWidth()) + m_MinimumSize.x = m_SizedNode->m_Bounds.GetWidth(); + if (m_MinimumSize.y == 0.0f && m_LastSize.y != m_SizedNode->m_Bounds.GetHeight()) + m_MinimumSize.y = m_SizedNode->m_Bounds.GetHeight(); + + auto minimumSize = ImMax(m_MinimumSize, m_StartBounds.GetSize() - m_StartGroupBounds.GetSize()); + + + auto newBounds = m_StartBounds; + + if ((m_Pivot & NodeRegion::Top) == NodeRegion::Top) + newBounds.Min.y = ImMin(newBounds.Max.y - minimumSize.y, Editor->AlignPointToGrid(newBounds.Min.y + dragOffset.y)); + if ((m_Pivot & NodeRegion::Bottom) == NodeRegion::Bottom) + newBounds.Max.y = ImMax(newBounds.Min.y + minimumSize.y, Editor->AlignPointToGrid(newBounds.Max.y + dragOffset.y)); + if ((m_Pivot & NodeRegion::Left) == NodeRegion::Left) + newBounds.Min.x = ImMin(newBounds.Max.x - minimumSize.x, Editor->AlignPointToGrid(newBounds.Min.x + dragOffset.x)); + if ((m_Pivot & NodeRegion::Right) == NodeRegion::Right) + newBounds.Max.x = ImMax(newBounds.Min.x + minimumSize.x, Editor->AlignPointToGrid(newBounds.Max.x + dragOffset.x)); + + FloorRect(newBounds); + + m_LastSize = newBounds.GetSize(); + + m_SizedNode->m_Bounds = newBounds; + m_SizedNode->m_GroupBounds = newBounds; + m_SizedNode->m_GroupBounds.Min.x -= m_StartBounds.Min.x - m_StartGroupBounds.Min.x; + m_SizedNode->m_GroupBounds.Min.y -= m_StartBounds.Min.y - m_StartGroupBounds.Min.y; + m_SizedNode->m_GroupBounds.Max.x -= m_StartBounds.Max.x - m_StartGroupBounds.Max.x; + m_SizedNode->m_GroupBounds.Max.y -= m_StartBounds.Max.y - m_StartGroupBounds.Max.y; + } + else if (!control.ActiveNode) + { + m_Clean = true; + m_IsActive = false; + return true; + } + + return m_IsActive; +} + +void ed::SizeAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + auto getObjectName = [](Object* object) + { + if (!object) return ""; + else if (object->AsNode()) return "Node"; + else if (object->AsPin()) return "Pin"; + else if (object->AsLink()) return "Link"; + else return ""; + }; + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); + ImGui::Text(" Node: %s (%p)", getObjectName(m_SizedNode), m_SizedNode ? m_SizedNode->m_ID.AsPointer() : nullptr); + if (m_SizedNode && m_IsActive) + { + ImGui::Text(" Bounds: { x=%g y=%g w=%g h=%g }", m_SizedNode->m_Bounds.Min.x, m_SizedNode->m_Bounds.Min.y, m_SizedNode->m_Bounds.GetWidth(), m_SizedNode->m_Bounds.GetHeight()); + ImGui::Text(" Group Bounds: { x=%g y=%g w=%g h=%g }", m_SizedNode->m_GroupBounds.Min.x, m_SizedNode->m_GroupBounds.Min.y, m_SizedNode->m_GroupBounds.GetWidth(), m_SizedNode->m_GroupBounds.GetHeight()); + ImGui::Text(" Start Bounds: { x=%g y=%g w=%g h=%g }", m_StartBounds.Min.x, m_StartBounds.Min.y, m_StartBounds.GetWidth(), m_StartBounds.GetHeight()); + ImGui::Text(" Start Group Bounds: { x=%g y=%g w=%g h=%g }", m_StartGroupBounds.Min.x, m_StartGroupBounds.Min.y, m_StartGroupBounds.GetWidth(), m_StartGroupBounds.GetHeight()); + ImGui::Text(" Minimum Size: { w=%g h=%g }", m_MinimumSize.x, m_MinimumSize.y); + ImGui::Text(" Last Size: { w=%g h=%g }", m_LastSize.x, m_LastSize.y); + } +} + +ed::NodeRegion ed::SizeAction::GetRegion(Node* node) +{ + return node->GetRegion(ImGui::GetMousePos()); +} + +ImGuiMouseCursor ed::SizeAction::ChooseCursor(NodeRegion region) +{ + switch (region) + { + default: + case NodeRegion::Center: + return ImGuiMouseCursor_Arrow; + + case NodeRegion::Top: + case NodeRegion::Bottom: + return ImGuiMouseCursor_ResizeNS; + + case NodeRegion::Left: + case NodeRegion::Right: + return ImGuiMouseCursor_ResizeEW; + + case NodeRegion::TopLeft: + case NodeRegion::BottomRight: + return ImGuiMouseCursor_ResizeNWSE; + + case NodeRegion::TopRight: + case NodeRegion::BottomLeft: + return ImGuiMouseCursor_ResizeNESW; + } +} + + + + +//------------------------------------------------------------------------------ +// +// Drag Action +// +//------------------------------------------------------------------------------ +ed::DragAction::DragAction(EditorContext* editor): + EditorAction(editor), + m_IsActive(false), + m_Clear(false), + m_DraggedObject(nullptr) +{ +} + +ed::EditorAction::AcceptResult ed::DragAction::Accept(const Control& control) +{ + IM_ASSERT(!m_IsActive); + + if (m_IsActive) + return False; + + if (Editor->CanAcceptUserInput() && control.ActiveObject && ImGui::IsMouseDragging(Editor->GetConfig().DragButtonIndex, 1)) + { + if (!control.ActiveObject->AcceptDrag()) + return False; + + m_DraggedObject = control.ActiveObject; + + m_Objects.resize(0); + m_Objects.push_back(m_DraggedObject); + + if (Editor->IsSelected(m_DraggedObject)) + { + for (auto selectedObject : Editor->GetSelectedObjects()) + if (auto selectedNode = selectedObject->AsNode()) + if (selectedNode != m_DraggedObject && selectedNode->AcceptDrag()) + m_Objects.push_back(selectedNode); + } + + auto& io = ImGui::GetIO(); + if (!io.KeyShift) + { + std::vector groupedNodes; + for (auto object : m_Objects) + if (auto node = object->AsNode()) + node->GetGroupedNodes(groupedNodes, true); + + auto isAlreadyPicked = [this](Node* node) + { + return std::find(m_Objects.begin(), m_Objects.end(), node) != m_Objects.end(); + }; + + for (auto candidate : groupedNodes) + if (!isAlreadyPicked(candidate) && candidate->AcceptDrag()) + m_Objects.push_back(candidate); + } + + m_IsActive = true; + } + else if (control.HotNode && IsGroup(control.HotNode) && control.HotNode->GetRegion(ImGui::GetMousePos()) == NodeRegion::Header) + { + return Possible; + } + + return m_IsActive ? True : False; +} + +bool ed::DragAction::Process(const Control& control) +{ + if (m_Clear) + { + m_Clear = false; + + for (auto object : m_Objects) + { + if (object->EndDrag()) + Editor->MakeDirty(SaveReasonFlags::Position | SaveReasonFlags::User, object->AsNode()); + } + + m_Objects.resize(0); + + m_DraggedObject = nullptr; + } + + if (!m_IsActive) + return false; + + if (control.ActiveObject == m_DraggedObject) + { + auto dragOffset = ImGui::GetMouseDragDelta(Editor->GetConfig().DragButtonIndex, 0.0f); + + auto draggedOrigin = m_DraggedObject->DragStartLocation(); + auto alignPivot = ImVec2(0, 0); + + // TODO: Move this experimental alignment to closes pivot out of internals to node API + if (auto draggedNode = m_DraggedObject->AsNode()) + { + float x = FLT_MAX; + float y = FLT_MAX; + + auto testPivot = [this, &x, &y, &draggedOrigin, &dragOffset, &alignPivot](const ImVec2& pivot) + { + auto initial = draggedOrigin + dragOffset + pivot; + auto candidate = Editor->AlignPointToGrid(initial) - draggedOrigin - pivot; + + if (ImFabs(candidate.x) < ImFabs(ImMin(x, FLT_MAX))) + { + x = candidate.x; + alignPivot.x = pivot.x; + } + + if (ImFabs(candidate.y) < ImFabs(ImMin(y, FLT_MAX))) + { + y = candidate.y; + alignPivot.y = pivot.y; + } + }; + + for (auto pin = draggedNode->m_LastPin; pin; pin = pin->m_PreviousPin) + { + auto pivot = pin->m_Pivot.GetCenter() - draggedNode->m_Bounds.Min; + testPivot(pivot); + } + + //testPivot(point(0, 0)); + } + + auto alignedOffset = Editor->AlignPointToGrid(draggedOrigin + dragOffset + alignPivot) - draggedOrigin - alignPivot; + + if (!ImGui::GetIO().KeyAlt) + dragOffset = alignedOffset; + + for (auto object : m_Objects) + object->UpdateDrag(dragOffset); + } + else if (!control.ActiveObject) + { + m_Clear = true; + + m_IsActive = false; + return true; + } + + return m_IsActive; +} + +void ed::DragAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + auto getObjectName = [](Object* object) + { + if (!object) return ""; + else if (object->AsNode()) return "Node"; + else if (object->AsPin()) return "Pin"; + else if (object->AsLink()) return "Link"; + else return ""; + }; + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); + ImGui::Text(" Node: %s (%p)", getObjectName(m_DraggedObject), m_DraggedObject ? m_DraggedObject->ID().AsPointer() : nullptr); +} + + + + +//------------------------------------------------------------------------------ +// +// Select Action +// +//------------------------------------------------------------------------------ +ed::SelectAction::SelectAction(EditorContext* editor): + EditorAction(editor), + m_IsActive(false), + m_SelectGroups(false), + m_SelectLinkMode(false), + m_CommitSelection(false), + m_StartPoint(), + m_Animation(editor) +{ +} + +ed::EditorAction::AcceptResult ed::SelectAction::Accept(const Control& control) +{ + IM_ASSERT(!m_IsActive); + + if (m_IsActive) + return False; + + auto& io = ImGui::GetIO(); + m_SelectGroups = io.KeyShift; + m_SelectLinkMode = io.KeyAlt; + + m_SelectedObjectsAtStart.clear(); + + if (Editor->CanAcceptUserInput() && control.BackgroundHot && ImGui::IsMouseDragging(Editor->GetConfig().SelectButtonIndex, 1)) + { + m_IsActive = true; + m_StartPoint = ImGui_GetMouseClickPos(Editor->GetConfig().SelectButtonIndex); + m_EndPoint = m_StartPoint; + + // Links and nodes cannot be selected together + if ((m_SelectLinkMode && Editor->IsAnyNodeSelected()) || + (!m_SelectLinkMode && Editor->IsAnyLinkSelected())) + { + Editor->ClearSelection(); + } + + if (io.KeyCtrl) + m_SelectedObjectsAtStart = Editor->GetSelectedObjects(); + } + else if (control.BackgroundClickButtonIndex == Editor->GetConfig().SelectButtonIndex) + { + Editor->ClearSelection(); + } + else + { + Object* clickedObject = control.ClickedNode ? static_cast(control.ClickedNode) : static_cast(control.ClickedLink); + + if (clickedObject) + { + // Links and nodes cannot be selected together + if ((clickedObject->AsLink() && Editor->IsAnyNodeSelected()) || + (clickedObject->AsNode() && Editor->IsAnyLinkSelected())) + { + Editor->ClearSelection(); + } + + if (io.KeyCtrl) + Editor->ToggleObjectSelection(clickedObject); + else + Editor->SetSelectedObject(clickedObject); + } + } + + if (m_IsActive) + m_Animation.Stop(); + + return m_IsActive ? True : False; +} + +bool ed::SelectAction::Process(const Control& control) +{ + IM_UNUSED(control); + + if (m_CommitSelection) + { + Editor->ClearSelection(); + for (auto object : m_CandidateObjects) + Editor->SelectObject(object); + + m_CandidateObjects.clear(); + + m_CommitSelection = false; + } + + if (!m_IsActive) + return false; + + if (ImGui::IsMouseDragging(Editor->GetConfig().SelectButtonIndex, 0)) + { + m_EndPoint = ImGui::GetMousePos(); + + auto topLeft = ImVec2(std::min(m_StartPoint.x, m_EndPoint.x), std::min(m_StartPoint.y, m_EndPoint.y)); + auto bottomRight = ImVec2(ImMax(m_StartPoint.x, m_EndPoint.x), ImMax(m_StartPoint.y, m_EndPoint.y)); + auto rect = ImRect(topLeft, bottomRight); + if (rect.GetWidth() <= 0) + rect.Max.x = rect.Min.x + 1; + if (rect.GetHeight() <= 0) + rect.Max.y = rect.Min.y + 1; + + vector nodes; + vector links; + + if (m_SelectLinkMode) + { + Editor->FindLinksInRect(rect, links); + m_CandidateObjects.assign(links.begin(), links.end()); + } + else + { + Editor->FindNodesInRect(rect, nodes); + m_CandidateObjects.assign(nodes.begin(), nodes.end()); + + if (m_SelectGroups) + { + auto endIt = std::remove_if(m_CandidateObjects.begin(), m_CandidateObjects.end(), [](Object* object) { return !IsGroup(object->AsNode()); }); + m_CandidateObjects.erase(endIt, m_CandidateObjects.end()); + } + else + { + auto endIt = std::remove_if(m_CandidateObjects.begin(), m_CandidateObjects.end(), [](Object* object) { return IsGroup(object->AsNode()); }); + m_CandidateObjects.erase(endIt, m_CandidateObjects.end()); + } + } + + m_CandidateObjects.insert(m_CandidateObjects.end(), m_SelectedObjectsAtStart.begin(), m_SelectedObjectsAtStart.end()); + std::sort(m_CandidateObjects.begin(), m_CandidateObjects.end()); + m_CandidateObjects.erase(std::unique(m_CandidateObjects.begin(), m_CandidateObjects.end()), m_CandidateObjects.end()); + } + else + { + m_IsActive = false; + + m_Animation.Play(c_SelectionFadeOutDuration); + + m_CommitSelection = true; + + return true; + } + + return m_IsActive; +} + +void ed::SelectAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); +} + +void ed::SelectAction::Draw(ImDrawList* drawList) +{ + if (!m_IsActive && !m_Animation.IsPlaying()) + return; + + const auto alpha = m_Animation.IsPlaying() ? ImEasing::EaseOutQuad(1.0f, -1.0f, m_Animation.GetProgress()) : 1.0f; + + const auto fillColor = Editor->GetColor(m_SelectLinkMode ? StyleColor_LinkSelRect : StyleColor_NodeSelRect, alpha); + const auto outlineColor = Editor->GetColor(m_SelectLinkMode ? StyleColor_LinkSelRectBorder : StyleColor_NodeSelRectBorder, alpha); + + drawList->ChannelsSetCurrent(c_BackgroundChannel_SelectionRect); + + auto min = ImVec2(std::min(m_StartPoint.x, m_EndPoint.x), std::min(m_StartPoint.y, m_EndPoint.y)); + auto max = ImVec2(ImMax(m_StartPoint.x, m_EndPoint.x), ImMax(m_StartPoint.y, m_EndPoint.y)); + + drawList->AddRectFilled(min, max, fillColor); + drawList->AddRect(min, max, outlineColor); +} + + + + +//------------------------------------------------------------------------------ +// +// Context Menu Action +// +//------------------------------------------------------------------------------ +ed::ContextMenuAction::ContextMenuAction(EditorContext* editor): + EditorAction(editor), + m_CandidateMenu(Menu::None), + m_CurrentMenu(Menu::None), + m_ContextId() +{ +} + +ed::EditorAction::AcceptResult ed::ContextMenuAction::Accept(const Control& control) +{ + const auto isPressed = ImGui::IsMouseClicked(Editor->GetConfig().ContextMenuButtonIndex); + const auto isReleased = ImGui::IsMouseReleased(Editor->GetConfig().ContextMenuButtonIndex); + const auto isDragging = ImGui::IsMouseDragging(Editor->GetConfig().ContextMenuButtonIndex, 1); + + if (isPressed || isReleased || isDragging) + { + Menu candidateMenu = ContextMenuAction::None; + ObjectId contextId; + + if (auto hotObejct = control.HotObject) + { + if (hotObejct->AsNode()) + candidateMenu = Node; + else if (hotObejct->AsPin()) + candidateMenu = Pin; + else if (hotObejct->AsLink()) + candidateMenu = Link; + + if (candidateMenu != None) + contextId = hotObejct->ID(); + } + else if (control.BackgroundHot) + candidateMenu = Background; + + if (isPressed) + { + m_CandidateMenu = candidateMenu; + m_ContextId = contextId; + return Possible; + } + else if (isReleased && m_CandidateMenu == candidateMenu && m_ContextId == contextId) + { + m_CurrentMenu = m_CandidateMenu; + m_CandidateMenu = ContextMenuAction::None; + return True; + } + else + { + m_CandidateMenu = None; + m_CurrentMenu = None; + m_ContextId = ObjectId(); + return False; + } + } + + return False; +} + +bool ed::ContextMenuAction::Process(const Control& control) +{ + IM_UNUSED(control); + + m_CandidateMenu = None; + m_CurrentMenu = None; + m_ContextId = ObjectId(); + return false; +} + +void ed::ContextMenuAction::Reject() +{ + m_CandidateMenu = None; + m_CurrentMenu = None; + m_ContextId = ObjectId(); +} + +void ed::ContextMenuAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + auto getMenuName = [](Menu menu) + { + switch (menu) + { + default: + case None: return "None"; + case Node: return "Node"; + case Pin: return "Pin"; + case Link: return "Link"; + case Background: return "Background"; + } + }; + + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Menu: %s", getMenuName(m_CurrentMenu)); +} + +bool ed::ContextMenuAction::ShowNodeContextMenu(NodeId* nodeId) +{ + if (m_CurrentMenu != Node) + return false; + + *nodeId = m_ContextId.AsNodeId(); + Editor->SetUserContext(); + return true; +} + +bool ed::ContextMenuAction::ShowPinContextMenu(PinId* pinId) +{ + if (m_CurrentMenu != Pin) + return false; + + *pinId = m_ContextId.AsPinId(); + Editor->SetUserContext(); + return true; +} + +bool ed::ContextMenuAction::ShowLinkContextMenu(LinkId* linkId) +{ + if (m_CurrentMenu != Link) + return false; + + *linkId = m_ContextId.AsLinkId(); + Editor->SetUserContext(); + return true; +} + +bool ed::ContextMenuAction::ShowBackgroundContextMenu() +{ + if (m_CurrentMenu != Background) + return false; + + Editor->SetUserContext(); + return true; +} + + + + +//------------------------------------------------------------------------------ +// +// Cut/Copy/Paste Action +// +//------------------------------------------------------------------------------ +ed::ShortcutAction::ShortcutAction(EditorContext* editor): + EditorAction(editor), + m_IsActive(false), + m_InAction(false), + m_CurrentAction(Action::None), + m_Context() +{ +} + +ed::EditorAction::AcceptResult ed::ShortcutAction::Accept(const Control& control) +{ + if (!Editor->IsFocused() || !Editor->AreShortcutsEnabled()) + return False; + + Action candidateAction = None; + + auto& io = ImGui::GetIO(); + if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) + candidateAction = Cut; + if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + candidateAction = Copy; + if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) + candidateAction = Paste; + if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(GetKeyIndexForD())) + candidateAction = Duplicate; + if (!io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space))) + candidateAction = CreateNode; + + if (candidateAction != None) + { + if (candidateAction != Paste && candidateAction != CreateNode) + { + auto& selection = Editor->GetSelectedObjects(); + if (!selection.empty()) + { + // #TODO: Find a way to simplify logic. + + m_Context.assign(selection.begin(), selection.end()); + + // Expand groups + vector extra; + for (auto object : m_Context) + { + auto node = object->AsNode(); + if (IsGroup(node)) + node->GetGroupedNodes(extra, true); + } + + // Apply groups and remove duplicates + if (!extra.empty()) + { + m_Context.insert(m_Context.end(), extra.begin(), extra.end()); + std::sort(m_Context.begin(), m_Context.end()); + m_Context.erase(std::unique(m_Context.begin(), m_Context.end()), m_Context.end()); + } + } + else if (control.HotObject && control.HotObject->IsSelectable() && !IsGroup(control.HotObject->AsNode())) + { + m_Context.push_back(control.HotObject); + } + + if (m_Context.empty()) + return False; + + // Does copying only links make sense? + //const auto hasOnlyLinks = std::all_of(Context.begin(), Context.end(), [](Object* object) { return object->AsLink() != nullptr; }); + //if (hasOnlyLinks) + // return False; + + // If no links are selected, pick all links between nodes within context + const auto hasAnyLinks = std::any_of(m_Context.begin(), m_Context.end(), [](Object* object) { return object->AsLink() != nullptr; }); + if (!hasAnyLinks && m_Context.size() > 1) // one node cannot make connection to anything + { + // Collect nodes in sorted vector viable for binary search + std::vector> nodes; + + nodes.reserve(m_Context.size()); + std::for_each(m_Context.begin(), m_Context.end(), [&nodes](Object* object) { if (auto node = object->AsNode()) nodes.push_back({node->m_ID, node}); }); + + std::sort(nodes.begin(), nodes.end()); + + auto isNodeInContext = [&nodes](NodeId nodeId) + { + return std::binary_search(nodes.begin(), nodes.end(), ObjectWrapper{nodeId, nullptr}); + }; + + // Collect links connected to nodes and drop those reaching out of context + std::vector links; + + for (auto node : nodes) + Editor->FindLinksForNode(node.m_ID, links, true); + + // Remove duplicates + std::sort(links.begin(), links.end()); + links.erase(std::unique(links.begin(), links.end()), links.end()); + + // Drop out of context links + links.erase(std::remove_if(links.begin(), links.end(), [&isNodeInContext](Link* link) + { + return !isNodeInContext(link->m_StartPin->m_Node->m_ID) || !isNodeInContext(link->m_EndPin->m_Node->m_ID); + }), links.end()); + + // Append links and remove duplicates + m_Context.insert(m_Context.end(), links.begin(), links.end()); + } + } + else + m_Context.resize(0); + + m_IsActive = true; + m_CurrentAction = candidateAction; + + return True; + } + + return False; +} + +bool ed::ShortcutAction::Process(const Control& control) +{ + IM_UNUSED(control); + + m_IsActive = false; + m_CurrentAction = None; + m_Context.resize(0); + return false; +} + +void ed::ShortcutAction::Reject() +{ + m_IsActive = false; + m_CurrentAction = None; + m_Context.resize(0); +} + +void ed::ShortcutAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + auto getActionName = [](Action action) + { + switch (action) + { + default: + case None: return "None"; + case Cut: return "Cut"; + case Copy: return "Copy"; + case Paste: return "Paste"; + case Duplicate: return "Duplicate"; + case CreateNode: return "CreateNode"; + } + }; + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Action: %s", getActionName(m_CurrentAction)); +} + +bool ed::ShortcutAction::Begin() +{ + if (m_IsActive) + m_InAction = true; + return m_IsActive; +} + +void ed::ShortcutAction::End() +{ + if (m_IsActive) + m_InAction = false; +} + +bool ed::ShortcutAction::AcceptCut() +{ + IM_ASSERT(m_InAction); + return m_CurrentAction == Cut; +} + +bool ed::ShortcutAction::AcceptCopy() +{ + IM_ASSERT(m_InAction); + return m_CurrentAction == Copy; +} + +bool ed::ShortcutAction::AcceptPaste() +{ + IM_ASSERT(m_InAction); + return m_CurrentAction == Paste; +} + +bool ed::ShortcutAction::AcceptDuplicate() +{ + IM_ASSERT(m_InAction); + return m_CurrentAction == Duplicate; +} + +bool ed::ShortcutAction::AcceptCreateNode() +{ + IM_ASSERT(m_InAction); + return m_CurrentAction == CreateNode; +} + + + + +//------------------------------------------------------------------------------ +// +// Create Item Action +// +//------------------------------------------------------------------------------ +ed::CreateItemAction::CreateItemAction(EditorContext* editor): + EditorAction(editor), + m_InActive(false), + m_NextStage(None), + m_CurrentStage(None), + m_ItemType(NoItem), + m_UserAction(Unknown), + m_LinkColor(IM_COL32_WHITE), + m_LinkThickness(1.0f), + m_LinkStart(nullptr), + m_LinkEnd(nullptr), + + m_IsActive(false), + m_DraggedPin(nullptr), + + m_IsInGlobalSpace(false) +{ +} + +ed::EditorAction::AcceptResult ed::CreateItemAction::Accept(const Control& control) +{ + IM_ASSERT(!m_IsActive); + + if (m_IsActive) + return EditorAction::False; + + if (control.ActivePin && ImGui::IsMouseDragging(Editor->GetConfig().DragButtonIndex, 1)) + { + m_DraggedPin = control.ActivePin; + DragStart(m_DraggedPin); + + Editor->ClearSelection(); + } + else if (control.HotPin) + { + return EditorAction::Possible; + } + else + return EditorAction::False; + + m_IsActive = true; + + return EditorAction::True; +} + +bool ed::CreateItemAction::Process(const Control& control) +{ + IM_ASSERT(m_IsActive); + + if (!m_IsActive) + return false; + + if (m_DraggedPin && control.ActivePin == m_DraggedPin && (m_CurrentStage == Possible)) + { + const auto draggingFromSource = (m_DraggedPin->m_Kind == PinKind::Output); + + ed::Pin cursorPin(Editor, 0, draggingFromSource ? PinKind::Input : PinKind::Output); + cursorPin.m_Pivot = ImRect(ImGui::GetMousePos(), ImGui::GetMousePos()); + cursorPin.m_Dir = -m_DraggedPin->m_Dir; + cursorPin.m_Strength = m_DraggedPin->m_Strength; + + ed::Link candidate(Editor, 0); + candidate.m_Color = m_LinkColor; + candidate.m_StartPin = draggingFromSource ? m_DraggedPin : &cursorPin; + candidate.m_EndPin = draggingFromSource ? &cursorPin : m_DraggedPin; + + ed::Pin*& freePin = draggingFromSource ? candidate.m_EndPin : candidate.m_StartPin; + + if (control.HotPin) + { + DropPin(control.HotPin); + + if (m_UserAction == UserAccept) + freePin = control.HotPin; + } + else if (control.BackgroundHot) + DropNode(); + else + DropNothing(); + + auto drawList = Editor->GetDrawList(); + drawList->ChannelsSetCurrent(c_LinkChannel_NewLink); + + candidate.UpdateEndpoints(); + candidate.Draw(drawList, m_LinkColor, m_LinkThickness); + } + else if (m_CurrentStage == Possible || !control.ActivePin) + { + if (!Editor->CanAcceptUserInput()) + { + m_DraggedPin = nullptr; + DropNothing(); + } + + DragEnd(); + m_IsActive = false; + } + + return m_IsActive; +} + +void ed::CreateItemAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + auto getStageName = [](Stage stage) + { + switch (stage) + { + case None: return "None"; + case Possible: return "Possible"; + case Create: return "Create"; + default: return ""; + } + }; + + auto getActionName = [](Action action) + { + switch (action) + { + default: + case Unknown: return "Unknown"; + case UserReject: return "Reject"; + case UserAccept: return "Accept"; + } + }; + + auto getItemName = [](Type item) + { + switch (item) + { + default: + case NoItem: return "None"; + case Node: return "Node"; + case Link: return "Link"; + } + }; + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Stage: %s", getStageName(m_CurrentStage)); + ImGui::Text(" User Action: %s", getActionName(m_UserAction)); + ImGui::Text(" Item Type: %s", getItemName(m_ItemType)); +} + +void ed::CreateItemAction::SetStyle(ImU32 color, float thickness) +{ + m_LinkColor = color; + m_LinkThickness = thickness; +} + +bool ed::CreateItemAction::Begin() +{ + IM_ASSERT(false == m_InActive); + + m_InActive = true; + m_CurrentStage = m_NextStage; + m_UserAction = Unknown; + m_LinkColor = IM_COL32_WHITE; + m_LinkThickness = 1.0f; + + if (m_CurrentStage == None) + return false; + + m_LastChannel = Editor->GetDrawList()->_Splitter._Current; + + return true; +} + +void ed::CreateItemAction::End() +{ + IM_ASSERT(m_InActive); + + if (m_IsInGlobalSpace) + { + ImGui::PopClipRect(); + Editor->Resume(SuspendFlags::KeepSplitter); + + auto currentChannel = Editor->GetDrawList()->_Splitter._Current; + if (currentChannel != m_LastChannel) + Editor->GetDrawList()->ChannelsSetCurrent(m_LastChannel); + + m_IsInGlobalSpace = false; + } + + m_InActive = false; +} + +void ed::CreateItemAction::DragStart(Pin* startPin) +{ + IM_ASSERT(!m_InActive); + + m_NextStage = Possible; + m_LinkStart = startPin; + m_LinkEnd = nullptr; +} + +void ed::CreateItemAction::DragEnd() +{ + IM_ASSERT(!m_InActive); + + if (m_CurrentStage == Possible && m_UserAction == UserAccept) + { + m_NextStage = Create; + } + else + { + m_NextStage = None; + m_ItemType = NoItem; + m_LinkStart = nullptr; + m_LinkEnd = nullptr; + } +} + +void ed::CreateItemAction::DropPin(Pin* endPin) +{ + IM_ASSERT(!m_InActive); + + m_ItemType = Link; + m_LinkEnd = endPin; +} + +void ed::CreateItemAction::DropNode() +{ + IM_ASSERT(!m_InActive); + + m_ItemType = Node; + m_LinkEnd = nullptr; +} + +void ed::CreateItemAction::DropNothing() +{ + IM_ASSERT(!m_InActive); + + m_ItemType = NoItem; + m_LinkEnd = nullptr; +} + +ed::CreateItemAction::Result ed::CreateItemAction::RejectItem() +{ + IM_ASSERT(m_InActive); + + if (!m_InActive || m_CurrentStage == None || m_ItemType == NoItem) + return Indeterminate; + + m_UserAction = UserReject; + + return True; +} + +ed::CreateItemAction::Result ed::CreateItemAction::AcceptItem() +{ + IM_ASSERT(m_InActive); + + if (!m_InActive || m_CurrentStage == None || m_ItemType == NoItem) + return Indeterminate; + + m_UserAction = UserAccept; + + if (m_CurrentStage == Create) + { + m_NextStage = None; + m_ItemType = NoItem; + m_LinkStart = nullptr; + m_LinkEnd = nullptr; + return True; + } + else + return False; +} + +ed::CreateItemAction::Result ed::CreateItemAction::QueryLink(PinId* startId, PinId* endId) +{ + IM_ASSERT(m_InActive); + + if (!m_InActive || m_CurrentStage == None || m_ItemType != Link) + return Indeterminate; + + auto linkStartId = m_LinkStart->m_ID; + auto linkEndId = m_LinkEnd->m_ID; + + *startId = linkStartId; + *endId = linkEndId; + + Editor->SetUserContext(true); + + if (!m_IsInGlobalSpace) + { + Editor->Suspend(SuspendFlags::KeepSplitter); + + auto rect = Editor->GetRect(); + ImGui::PushClipRect(rect.Min + ImVec2(1, 1), rect.Max - ImVec2(1, 1), false); + m_IsInGlobalSpace = true; + } + + return True; +} + +ed::CreateItemAction::Result ed::CreateItemAction::QueryNode(PinId* pinId) +{ + IM_ASSERT(m_InActive); + + if (!m_InActive || m_CurrentStage == None || m_ItemType != Node) + return Indeterminate; + + *pinId = m_LinkStart ? m_LinkStart->m_ID : 0; + + Editor->SetUserContext(true); + + if (!m_IsInGlobalSpace) + { + Editor->Suspend(SuspendFlags::KeepSplitter); + + auto rect = Editor->GetRect(); + ImGui::PushClipRect(rect.Min + ImVec2(1, 1), rect.Max - ImVec2(1, 1), false); + m_IsInGlobalSpace = true; + } + + return True; +} + + + + +//------------------------------------------------------------------------------ +// +// Delete Items Action +// +//------------------------------------------------------------------------------ +ed::DeleteItemsAction::DeleteItemsAction(EditorContext* editor): + EditorAction(editor), + m_IsActive(false), + m_InInteraction(false), + m_CurrentItemType(Unknown), + m_UserAction(Undetermined) +{ +} + +void ed::DeleteItemsAction::DeleteDeadLinks(NodeId nodeId) +{ + vector links; + Editor->FindLinksForNode(nodeId, links, true); + for (auto link : links) + { + link->m_DeleteOnNewFrame = true; + + auto it = std::find(m_CandidateObjects.begin(), m_CandidateObjects.end(), link); + if (it != m_CandidateObjects.end()) + continue; + + m_CandidateObjects.push_back(link); + } +} + +void ed::DeleteItemsAction::DeleteDeadPins(NodeId nodeId) +{ + auto node = Editor->FindNode(nodeId); + if (!node) + return; + + for (auto pin = node->m_LastPin; pin; pin = pin->m_PreviousPin) + pin->m_DeleteOnNewFrame = true; +} + +ed::EditorAction::AcceptResult ed::DeleteItemsAction::Accept(const Control& control) +{ + IM_ASSERT(!m_IsActive); + + if (m_IsActive) + return False; + + auto& io = ImGui::GetIO(); + if (Editor->CanAcceptUserInput() && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)) && Editor->AreShortcutsEnabled()) + { + auto& selection = Editor->GetSelectedObjects(); + if (!selection.empty()) + { + m_CandidateObjects = selection; + m_IsActive = true; + return True; + } + } + else if (control.ClickedLink && io.KeyAlt) + { + m_CandidateObjects.clear(); + m_CandidateObjects.push_back(control.ClickedLink); + m_IsActive = true; + return True; + } + + else if (!m_ManuallyDeletedObjects.empty()) + { + m_CandidateObjects = m_ManuallyDeletedObjects; + m_ManuallyDeletedObjects.clear(); + m_IsActive = true; + return True; + } + + return m_IsActive ? True : False; +} + +bool ed::DeleteItemsAction::Process(const Control& control) +{ + IM_UNUSED(control); + + if (!m_IsActive) + return false; + + m_IsActive = false; + return true; +} + +void ed::DeleteItemsAction::ShowMetrics() +{ + EditorAction::ShowMetrics(); + + //auto getObjectName = [](Object* object) + //{ + // if (!object) return ""; + // else if (object->AsNode()) return "Node"; + // else if (object->AsPin()) return "Pin"; + // else if (object->AsLink()) return "Link"; + // else return ""; + //}; + + ImGui::Text("%s:", GetName()); + ImGui::Text(" Active: %s", m_IsActive ? "yes" : "no"); + //ImGui::Text(" Node: %s (%d)", getObjectName(DeleteItemsgedNode), DeleteItemsgedNode ? DeleteItemsgedNode->ID : 0); +} + +bool ed::DeleteItemsAction::Add(Object* object) +{ + if (Editor->GetCurrentAction() != nullptr) + return false; + + m_ManuallyDeletedObjects.push_back(object); + + return true; +} + +bool ed::DeleteItemsAction::Begin() +{ + if (!m_IsActive) + return false; + + IM_ASSERT(!m_InInteraction); + m_InInteraction = true; + + m_CurrentItemType = Unknown; + m_UserAction = Undetermined; + + return m_IsActive; +} + +void ed::DeleteItemsAction::End() +{ + if (!m_IsActive) + return; + + IM_ASSERT(m_InInteraction); + m_InInteraction = false; +} + +bool ed::DeleteItemsAction::QueryLink(LinkId* linkId, PinId* startId, PinId* endId) +{ + ObjectId objectId; + if (!QueryItem(&objectId, Link)) + return false; + + if (auto id = objectId.AsLinkId()) + *linkId = id; + else + return false; + + if (startId || endId) + { + auto link = Editor->FindLink(*linkId); + if (startId) + *startId = link->m_StartPin->m_ID; + if (endId) + *endId = link->m_EndPin->m_ID; + } + + return true; +} + +bool ed::DeleteItemsAction::QueryNode(NodeId* nodeId) +{ + ObjectId objectId; + if (!QueryItem(&objectId, Node)) + return false; + + if (auto id = objectId.AsNodeId()) + *nodeId = id; + else + return false; + + return true; +} + +bool ed::DeleteItemsAction::QueryItem(ObjectId* itemId, IteratorType itemType) +{ + if (!m_InInteraction) + return false; + + if (m_CurrentItemType != itemType) + { + m_CurrentItemType = itemType; + m_CandidateItemIndex = 0; + } + else if (m_UserAction == Undetermined) + { + RejectItem(); + } + + m_UserAction = Undetermined; + + auto itemCount = (int)m_CandidateObjects.size(); + while (m_CandidateItemIndex < itemCount) + { + auto item = m_CandidateObjects[m_CandidateItemIndex]; + if (itemType == Node) + { + if (auto node = item->AsNode()) + { + *itemId = node->m_ID; + return true; + } + } + else if (itemType == Link) + { + if (auto link = item->AsLink()) + { + *itemId = link->m_ID; + return true; + } + } + + ++m_CandidateItemIndex; + } + + if (m_CandidateItemIndex == itemCount) + m_CurrentItemType = Unknown; + + return false; +} + +bool ed::DeleteItemsAction::AcceptItem(bool deleteDependencies) +{ + if (!m_InInteraction) + return false; + + m_UserAction = Accepted; + + RemoveItem(deleteDependencies); + + return true; +} + +void ed::DeleteItemsAction::RejectItem() +{ + if (!m_InInteraction) + return; + + m_UserAction = Rejected; + + DropCurrentItem(); +} + +void ed::DeleteItemsAction::RemoveItem(bool deleteDependencies) +{ + auto item = DropCurrentItem(); + + Editor->DeselectObject(item); + + Editor->RemoveSettings(item); + + item->m_DeleteOnNewFrame = true; + + if (deleteDependencies && m_CurrentItemType == Node) + { + auto node = item->ID().AsNodeId(); + DeleteDeadLinks(node); + DeleteDeadPins(node); + } + + if (m_CurrentItemType == Link) + Editor->NotifyLinkDeleted(item->AsLink()); +} + +ed::Object* ed::DeleteItemsAction::DropCurrentItem() +{ + auto item = m_CandidateObjects[m_CandidateItemIndex]; + m_CandidateObjects.erase(m_CandidateObjects.begin() + m_CandidateItemIndex); + + return item; +} + + + + +//------------------------------------------------------------------------------ +// +// Node Builder +// +//------------------------------------------------------------------------------ +ed::NodeBuilder::NodeBuilder(EditorContext* editor): + Editor(editor), + m_CurrentNode(nullptr), + m_CurrentPin(nullptr) +{ +} + +ed::NodeBuilder::~NodeBuilder() +{ + m_Splitter.ClearFreeMemory(); + m_PinSplitter.ClearFreeMemory(); +} + +void ed::NodeBuilder::Begin(NodeId nodeId) +{ + IM_ASSERT(nullptr == m_CurrentNode); + + m_CurrentNode = Editor->GetNode(nodeId); + + Editor->UpdateNodeState(m_CurrentNode); + + if (m_CurrentNode->m_CenterOnScreen) + { + auto bounds = Editor->GetViewRect(); + auto offset = bounds.GetCenter() - m_CurrentNode->m_Bounds.GetCenter(); + + if (ImLengthSqr(offset) > 0) + { + if (::IsGroup(m_CurrentNode)) + { + std::vector groupedNodes; + m_CurrentNode->GetGroupedNodes(groupedNodes); + groupedNodes.push_back(m_CurrentNode); + + for (auto node : groupedNodes) + { + node->m_Bounds.Translate(ImFloor(offset)); + node->m_GroupBounds.Translate(ImFloor(offset)); + Editor->MakeDirty(SaveReasonFlags::Position | SaveReasonFlags::User, node); + } + } + else + { + m_CurrentNode->m_Bounds.Translate(ImFloor(offset)); + m_CurrentNode->m_GroupBounds.Translate(ImFloor(offset)); + Editor->MakeDirty(SaveReasonFlags::Position | SaveReasonFlags::User, m_CurrentNode); + } + } + + m_CurrentNode->m_CenterOnScreen = false; + } + + // Position node on screen + ImGui::SetCursorScreenPos(m_CurrentNode->m_Bounds.Min); + + auto& editorStyle = Editor->GetStyle(); + + const auto alpha = ImGui::GetStyle().Alpha; + + m_CurrentNode->m_IsLive = true; + m_CurrentNode->m_LastPin = nullptr; + m_CurrentNode->m_Color = Editor->GetColor(StyleColor_NodeBg, alpha); + m_CurrentNode->m_BorderColor = Editor->GetColor(StyleColor_NodeBorder, alpha); + m_CurrentNode->m_BorderWidth = editorStyle.NodeBorderWidth; + m_CurrentNode->m_Rounding = editorStyle.NodeRounding; + m_CurrentNode->m_GroupColor = Editor->GetColor(StyleColor_GroupBg, alpha); + m_CurrentNode->m_GroupBorderColor = Editor->GetColor(StyleColor_GroupBorder, alpha); + m_CurrentNode->m_GroupBorderWidth = editorStyle.GroupBorderWidth; + m_CurrentNode->m_GroupRounding = editorStyle.GroupRounding; + m_CurrentNode->m_HighlightConnectedLinks = editorStyle.HighlightConnectedLinks != 0.0f; + + m_IsGroup = false; + + // Grow channel list and select user channel + if (auto drawList = Editor->GetDrawList()) + { + m_CurrentNode->m_Channel = drawList->_Splitter._Count; + ImDrawList_ChannelsGrow(drawList, drawList->_Splitter._Count + c_ChannelsPerNode); + drawList->ChannelsSetCurrent(m_CurrentNode->m_Channel + c_NodeContentChannel); + + m_Splitter.Clear(); + ImDrawList_SwapSplitter(drawList, m_Splitter); + } + + // Begin outer group + ImGui::BeginGroup(); + + // Apply frame padding. Begin inner group if necessary. + if (editorStyle.NodePadding.x != 0 || editorStyle.NodePadding.y != 0 || editorStyle.NodePadding.z != 0 || editorStyle.NodePadding.w != 0) + { + ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(editorStyle.NodePadding.x, editorStyle.NodePadding.y)); + ImGui::BeginGroup(); + } +} + +void ed::NodeBuilder::End() +{ + IM_ASSERT(nullptr != m_CurrentNode); + + if (auto drawList = Editor->GetDrawList()) + { + IM_ASSERT(drawList->_Splitter._Count == 1); // Did you forgot to call drawList->ChannelsMerge()? + ImDrawList_SwapSplitter(drawList, m_Splitter); + } + + // Apply frame padding. This must be done in this convoluted way if outer group + // size must contain inner group padding. + auto& editorStyle = Editor->GetStyle(); + if (editorStyle.NodePadding.x != 0 || editorStyle.NodePadding.y != 0 || editorStyle.NodePadding.z != 0 || editorStyle.NodePadding.w != 0) + { + ImGui::EndGroup(); + ImGui::SameLine(0, editorStyle.NodePadding.z); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + ImGui::Dummy(ImVec2(0, 0)); // bump cursor at the end of the line and move to next one + ImGui::Dummy(ImVec2(0, editorStyle.NodePadding.w)); // apply padding + ImGui::PopStyleVar(); + } + + // End outer group. + ImGui::EndGroup(); + + m_NodeRect = ImGui_GetItemRect(); + FloorRect(m_NodeRect); + + if (m_CurrentNode->m_Bounds.GetSize() != m_NodeRect.GetSize()) + { + m_CurrentNode->m_Bounds.Max = m_CurrentNode->m_Bounds.Min + m_NodeRect.GetSize(); + Editor->MakeDirty(SaveReasonFlags::Size, m_CurrentNode); + } + + if (m_IsGroup) + { + // Groups cannot have pins. Discard them. + for (auto pin = m_CurrentNode->m_LastPin; pin; pin = pin->m_PreviousPin) + pin->Reset(); + + m_CurrentNode->m_Type = NodeType::Group; + m_CurrentNode->m_GroupBounds = m_GroupBounds; + m_CurrentNode->m_LastPin = nullptr; + } + else + m_CurrentNode->m_Type = NodeType::Node; + + m_CurrentNode = nullptr; +} + +void ed::NodeBuilder::BeginPin(PinId pinId, PinKind kind) +{ + IM_ASSERT(nullptr != m_CurrentNode); + IM_ASSERT(nullptr == m_CurrentPin); + IM_ASSERT(false == m_IsGroup); + + auto& editorStyle = Editor->GetStyle(); + + m_CurrentPin = Editor->GetPin(pinId, kind); + m_CurrentPin->m_Node = m_CurrentNode; + + m_CurrentPin->m_IsLive = true; + m_CurrentPin->m_Color = Editor->GetColor(StyleColor_PinRect); + m_CurrentPin->m_BorderColor = Editor->GetColor(StyleColor_PinRectBorder); + m_CurrentPin->m_BorderWidth = editorStyle.PinBorderWidth; + m_CurrentPin->m_Rounding = editorStyle.PinRounding; + m_CurrentPin->m_Corners = static_cast(editorStyle.PinCorners); + m_CurrentPin->m_Radius = editorStyle.PinRadius; + m_CurrentPin->m_ArrowSize = editorStyle.PinArrowSize; + m_CurrentPin->m_ArrowWidth = editorStyle.PinArrowWidth; + m_CurrentPin->m_Dir = kind == PinKind::Output ? editorStyle.SourceDirection : editorStyle.TargetDirection; + m_CurrentPin->m_Strength = editorStyle.LinkStrength; + m_CurrentPin->m_SnapLinkToDir = editorStyle.SnapLinkToPinDir != 0.0f; + + m_CurrentPin->m_PreviousPin = m_CurrentNode->m_LastPin; + m_CurrentNode->m_LastPin = m_CurrentPin; + + m_PivotAlignment = editorStyle.PivotAlignment; + m_PivotSize = editorStyle.PivotSize; + m_PivotScale = editorStyle.PivotScale; + m_ResolvePinRect = true; + m_ResolvePivot = true; + + if (auto drawList = Editor->GetDrawList()) + { + m_PinSplitter.Clear(); + ImDrawList_SwapSplitter(drawList, m_PinSplitter); + } + + ImGui::BeginGroup(); +} + +void ed::NodeBuilder::EndPin() +{ + IM_ASSERT(nullptr != m_CurrentPin); + + if (auto drawList = Editor->GetDrawList()) + { + IM_ASSERT(drawList->_Splitter._Count == 1); // Did you forgot to call drawList->ChannelsMerge()? + ImDrawList_SwapSplitter(drawList, m_PinSplitter); + } + + ImGui::EndGroup(); + + if (m_ResolvePinRect) + m_CurrentPin->m_Bounds = ImGui_GetItemRect(); + + if (m_ResolvePivot) + { + auto& pinRect = m_CurrentPin->m_Bounds; + + if (m_PivotSize.x < 0) + m_PivotSize.x = pinRect.GetWidth(); + if (m_PivotSize.y < 0) + m_PivotSize.y = pinRect.GetHeight(); + + m_CurrentPin->m_Pivot.Min = pinRect.Min + ImMul(pinRect.GetSize(), m_PivotAlignment); + m_CurrentPin->m_Pivot.Max = m_CurrentPin->m_Pivot.Min + ImMul(m_PivotSize, m_PivotScale); + } + + // #debug: Draw pin bounds + //Editor->GetDrawList()->AddRect(m_CurrentPin->m_Bounds.Min, m_CurrentPin->m_Bounds.Max, IM_COL32(255, 255, 0, 255)); + + // #debug: Draw pin pivot rectangle + //Editor->GetDrawList()->AddRect(m_CurrentPin->m_Pivot.Min, m_CurrentPin->m_Pivot.Max, IM_COL32(255, 0, 255, 255)); + + m_CurrentPin = nullptr; +} + +void ed::NodeBuilder::PinRect(const ImVec2& a, const ImVec2& b) +{ + IM_ASSERT(nullptr != m_CurrentPin); + + m_CurrentPin->m_Bounds = ImRect(a, b); + FloorRect(m_CurrentPin->m_Bounds); + m_ResolvePinRect = false; +} + +void ed::NodeBuilder::PinPivotRect(const ImVec2& a, const ImVec2& b) +{ + IM_ASSERT(nullptr != m_CurrentPin); + + m_CurrentPin->m_Pivot = ImRect(a, b); + m_ResolvePivot = false; +} + +void ed::NodeBuilder::PinPivotSize(const ImVec2& size) +{ + IM_ASSERT(nullptr != m_CurrentPin); + + m_PivotSize = size; + m_ResolvePivot = true; +} + +void ed::NodeBuilder::PinPivotScale(const ImVec2& scale) +{ + IM_ASSERT(nullptr != m_CurrentPin); + + m_PivotScale = scale; + m_ResolvePivot = true; +} + +void ed::NodeBuilder::PinPivotAlignment(const ImVec2& alignment) +{ + IM_ASSERT(nullptr != m_CurrentPin); + + m_PivotAlignment = alignment; + m_ResolvePivot = true; +} + +void ed::NodeBuilder::Group(const ImVec2& size) +{ + IM_ASSERT(nullptr != m_CurrentNode); + IM_ASSERT(nullptr == m_CurrentPin); + IM_ASSERT(false == m_IsGroup); + + m_IsGroup = true; + + if (IsGroup(m_CurrentNode)) + ImGui::Dummy(m_CurrentNode->m_GroupBounds.GetSize()); + else + ImGui::Dummy(size); + + m_GroupBounds = ImGui_GetItemRect(); + FloorRect(m_GroupBounds); +} + +ImDrawList* ed::NodeBuilder::GetUserBackgroundDrawList() const +{ + return GetUserBackgroundDrawList(m_CurrentNode); +} + +ImDrawList* ed::NodeBuilder::GetUserBackgroundDrawList(Node* node) const +{ + if (node && node->m_IsLive) + { + auto drawList = Editor->GetDrawList(); + drawList->ChannelsSetCurrent(node->m_Channel + c_NodeUserBackgroundChannel); + return drawList; + } + else + return nullptr; +} + + + + +//------------------------------------------------------------------------------ +// +// Node Builder +// +//------------------------------------------------------------------------------ +ed::HintBuilder::HintBuilder(EditorContext* editor): + Editor(editor), + m_IsActive(false), + m_CurrentNode(nullptr) +{ +} + +bool ed::HintBuilder::Begin(NodeId nodeId) +{ + IM_ASSERT(nullptr == m_CurrentNode); + + auto& view = Editor->GetView(); + auto& rect = Editor->GetRect(); + + const float c_min_zoom = 0.75f; + const float c_max_zoom = 0.50f; + + if (view.Scale > 0.75f) + return false; + + auto node = Editor->FindNode(nodeId); + if (!IsGroup(node)) + return false; + + m_CurrentNode = node; + + m_LastChannel = Editor->GetDrawList()->_Splitter._Current; + + Editor->Suspend(SuspendFlags::KeepSplitter); + + const auto alpha = ImMax(0.0f, std::min(1.0f, (view.Scale - c_min_zoom) / (c_max_zoom - c_min_zoom))); + + Editor->GetDrawList()->ChannelsSetCurrent(c_UserChannel_HintsBackground); + ImGui::PushClipRect(rect.Min + ImVec2(1, 1), rect.Max - ImVec2(1, 1), false); + + Editor->GetDrawList()->ChannelsSetCurrent(c_UserChannel_Hints); + ImGui::PushClipRect(rect.Min + ImVec2(1, 1), rect.Max - ImVec2(1, 1), false); + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); + + m_IsActive = true; + + return true; +} + +void ed::HintBuilder::End() +{ + if (!m_IsActive) + return; + + ImGui::PopStyleVar(); + + Editor->GetDrawList()->ChannelsSetCurrent(c_UserChannel_Hints); + ImGui::PopClipRect(); + + Editor->GetDrawList()->ChannelsSetCurrent(c_UserChannel_HintsBackground); + ImGui::PopClipRect(); + + Editor->GetDrawList()->ChannelsSetCurrent(m_LastChannel); + + Editor->Resume(SuspendFlags::KeepSplitter); + + m_IsActive = false; + m_CurrentNode = nullptr; +} + +ImVec2 ed::HintBuilder::GetGroupMin() +{ + IM_ASSERT(nullptr != m_CurrentNode); + + return Editor->ToScreen(m_CurrentNode->m_Bounds.Min); +} + +ImVec2 ed::HintBuilder::GetGroupMax() +{ + IM_ASSERT(nullptr != m_CurrentNode); + + return Editor->ToScreen(m_CurrentNode->m_Bounds.Max); +} + +ImDrawList* ed::HintBuilder::GetForegroundDrawList() +{ + IM_ASSERT(nullptr != m_CurrentNode); + + auto drawList = Editor->GetDrawList(); + + drawList->ChannelsSetCurrent(c_UserChannel_Hints); + + return drawList; +} + +ImDrawList* ed::HintBuilder::GetBackgroundDrawList() +{ + IM_ASSERT(nullptr != m_CurrentNode); + + auto drawList = Editor->GetDrawList(); + + drawList->ChannelsSetCurrent(c_UserChannel_HintsBackground); + + return drawList; +} + + + + +//------------------------------------------------------------------------------ +// +// Style +// +//------------------------------------------------------------------------------ +void ed::Style::PushColor(StyleColor colorIndex, const ImVec4& color) +{ + ColorModifier modifier; + modifier.Index = colorIndex; + modifier.Value = Colors[colorIndex]; + m_ColorStack.push_back(modifier); + Colors[colorIndex] = color; +} + +void ed::Style::PopColor(int count) +{ + while (count > 0) + { + auto& modifier = m_ColorStack.back(); + Colors[modifier.Index] = modifier.Value; + m_ColorStack.pop_back(); + --count; + } +} + +void ed::Style::PushVar(StyleVar varIndex, float value) +{ + auto* var = GetVarFloatAddr(varIndex); + IM_ASSERT(var != nullptr); + VarModifier modifier; + modifier.Index = varIndex; + modifier.Value = ImVec4(*var, 0, 0, 0); + *var = value; + m_VarStack.push_back(modifier); +} + +void ed::Style::PushVar(StyleVar varIndex, const ImVec2& value) +{ + auto* var = GetVarVec2Addr(varIndex); + IM_ASSERT(var != nullptr); + VarModifier modifier; + modifier.Index = varIndex; + modifier.Value = ImVec4(var->x, var->y, 0, 0); + *var = value; + m_VarStack.push_back(modifier); +} + +void ed::Style::PushVar(StyleVar varIndex, const ImVec4& value) +{ + auto* var = GetVarVec4Addr(varIndex); + IM_ASSERT(var != nullptr); + VarModifier modifier; + modifier.Index = varIndex; + modifier.Value = *var; + *var = value; + m_VarStack.push_back(modifier); +} + +void ed::Style::PopVar(int count) +{ + while (count > 0) + { + auto& modifier = m_VarStack.back(); + if (auto floatValue = GetVarFloatAddr(modifier.Index)) + *floatValue = modifier.Value.x; + else if (auto vec2Value = GetVarVec2Addr(modifier.Index)) + *vec2Value = ImVec2(modifier.Value.x, modifier.Value.y); + else if (auto vec4Value = GetVarVec4Addr(modifier.Index)) + *vec4Value = modifier.Value; + m_VarStack.pop_back(); + --count; + } +} + +const char* ed::Style::GetColorName(StyleColor colorIndex) const +{ + switch (colorIndex) + { + case StyleColor_Bg: return "Bg"; + case StyleColor_Grid: return "Grid"; + case StyleColor_NodeBg: return "NodeBg"; + case StyleColor_NodeBorder: return "NodeBorder"; + case StyleColor_HovNodeBorder: return "HoveredNodeBorder"; + case StyleColor_SelNodeBorder: return "SelNodeBorder"; + case StyleColor_NodeSelRect: return "NodeSelRect"; + case StyleColor_NodeSelRectBorder: return "NodeSelRectBorder"; + case StyleColor_HovLinkBorder: return "HoveredLinkBorder"; + case StyleColor_SelLinkBorder: return "SelLinkBorder"; + case StyleColor_HighlightLinkBorder: return "HighlightLinkBorder"; + case StyleColor_LinkSelRect: return "LinkSelRect"; + case StyleColor_LinkSelRectBorder: return "LinkSelRectBorder"; + case StyleColor_PinRect: return "PinRect"; + case StyleColor_PinRectBorder: return "PinRectBorder"; + case StyleColor_Flow: return "Flow"; + case StyleColor_FlowMarker: return "FlowMarker"; + case StyleColor_GroupBg: return "GroupBg"; + case StyleColor_GroupBorder: return "GroupBorder"; + case StyleColor_Count: break; + } + + IM_ASSERT(0); + return "Unknown"; +} + +float* ed::Style::GetVarFloatAddr(StyleVar idx) +{ + switch (idx) + { + case StyleVar_NodeRounding: return &NodeRounding; + case StyleVar_NodeBorderWidth: return &NodeBorderWidth; + case StyleVar_HoveredNodeBorderWidth: return &HoveredNodeBorderWidth; + case StyleVar_SelectedNodeBorderWidth: return &SelectedNodeBorderWidth; + case StyleVar_PinRounding: return &PinRounding; + case StyleVar_PinBorderWidth: return &PinBorderWidth; + case StyleVar_LinkStrength: return &LinkStrength; + case StyleVar_ScrollDuration: return &ScrollDuration; + case StyleVar_FlowMarkerDistance: return &FlowMarkerDistance; + case StyleVar_FlowSpeed: return &FlowSpeed; + case StyleVar_FlowDuration: return &FlowDuration; + case StyleVar_PinCorners: return &PinCorners; + case StyleVar_PinRadius: return &PinRadius; + case StyleVar_PinArrowSize: return &PinArrowSize; + case StyleVar_PinArrowWidth: return &PinArrowWidth; + case StyleVar_GroupRounding: return &GroupRounding; + case StyleVar_GroupBorderWidth: return &GroupBorderWidth; + case StyleVar_HighlightConnectedLinks: return &HighlightConnectedLinks; + case StyleVar_SnapLinkToPinDir: return &SnapLinkToPinDir; + case StyleVar_HoveredNodeBorderOffset: return &HoverNodeBorderOffset; + case StyleVar_SelectedNodeBorderOffset: return &SelectedNodeBorderOffset; + default: return nullptr; + } +} + +ImVec2* ed::Style::GetVarVec2Addr(StyleVar idx) +{ + switch (idx) + { + case StyleVar_SourceDirection: return &SourceDirection; + case StyleVar_TargetDirection: return &TargetDirection; + case StyleVar_PivotAlignment: return &PivotAlignment; + case StyleVar_PivotSize: return &PivotSize; + case StyleVar_PivotScale: return &PivotScale; + default: return nullptr; + } +} + +ImVec4* ed::Style::GetVarVec4Addr(StyleVar idx) +{ + switch (idx) + { + case StyleVar_NodePadding: return &NodePadding; + default: return nullptr; + } +} + + + + +//------------------------------------------------------------------------------ +// +// Config +// +//------------------------------------------------------------------------------ +ed::Config::Config(const ax::NodeEditor::Config* config) +{ + if (config) + *static_cast(this) = *config; +} + +std::string ed::Config::Load() +{ + std::string data; + + if (LoadSettings) + { + const auto size = LoadSettings(nullptr, UserPointer); + if (size > 0) + { + data.resize(size); + LoadSettings(const_cast(data.data()), UserPointer); + } + } + else if (SettingsFile) + { + std::ifstream file(SettingsFile); + if (file) + { + file.seekg(0, std::ios_base::end); + auto size = static_cast(file.tellg()); + file.seekg(0, std::ios_base::beg); + + data.reserve(size); + data.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); + } + } + + return data; +} + +std::string ed::Config::LoadNode(NodeId nodeId) +{ + std::string data; + + if (LoadNodeSettings) + { + const auto size = LoadNodeSettings(nodeId, nullptr, UserPointer); + if (size > 0) + { + data.resize(size); + LoadNodeSettings(nodeId, const_cast(data.data()), UserPointer); + } + } + + return data; +} + +void ed::Config::BeginSave() +{ + if (BeginSaveSession) + BeginSaveSession(UserPointer); +} + +bool ed::Config::Save(const std::string& data, SaveReasonFlags flags) +{ + if (SaveSettings) + { + return SaveSettings(data.c_str(), data.size(), flags, UserPointer); + } + else if (SettingsFile) + { + std::ofstream settingsFile(SettingsFile); + if (settingsFile) + settingsFile << data; + + return !!settingsFile; + } + + return false; +} + +bool ed::Config::SaveNode(NodeId nodeId, const std::string& data, SaveReasonFlags flags) +{ + if (SaveNodeSettings) + return SaveNodeSettings(nodeId, data.c_str(), data.size(), flags, UserPointer); + + return false; +} + +void ed::Config::EndSave() +{ + if (EndSaveSession) + EndSaveSession(UserPointer); +} diff --git a/cpp/vendor/imgui-node-editor/imgui_node_editor.h b/cpp/vendor/imgui-node-editor/imgui_node_editor.h new file mode 100644 index 00000000..c79f41ad --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_node_editor.h @@ -0,0 +1,530 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_NODE_EDITOR_H__ +# define __IMGUI_NODE_EDITOR_H__ +# pragma once + + +//------------------------------------------------------------------------------ +# include +# include // std::uintXX_t +# include // std::move + + +//------------------------------------------------------------------------------ +# define IMGUI_NODE_EDITOR_VERSION "0.9.4" +# define IMGUI_NODE_EDITOR_VERSION_NUM 000904 + + +//------------------------------------------------------------------------------ +#ifndef IMGUI_NODE_EDITOR_API +#define IMGUI_NODE_EDITOR_API +#endif + + +//------------------------------------------------------------------------------ +namespace ax { +namespace NodeEditor { + + +//------------------------------------------------------------------------------ +struct NodeId; +struct LinkId; +struct PinId; + + +//------------------------------------------------------------------------------ +enum class PinKind +{ + Input, + Output +}; + +enum class FlowDirection +{ + Forward, + Backward +}; + +enum class CanvasSizeMode +{ + FitVerticalView, // Previous view will be scaled to fit new view on Y axis + FitHorizontalView, // Previous view will be scaled to fit new view on X axis + CenterOnly, // Previous view will be centered on new view +}; + + +//------------------------------------------------------------------------------ +enum class SaveReasonFlags: uint32_t +{ + None = 0x00000000, + Navigation = 0x00000001, + Position = 0x00000002, + Size = 0x00000004, + Selection = 0x00000008, + AddNode = 0x00000010, + RemoveNode = 0x00000020, + User = 0x00000040 +}; + +inline SaveReasonFlags operator |(SaveReasonFlags lhs, SaveReasonFlags rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } +inline SaveReasonFlags operator &(SaveReasonFlags lhs, SaveReasonFlags rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } + +using ConfigSaveSettings = bool (*)(const char* data, size_t size, SaveReasonFlags reason, void* userPointer); +using ConfigLoadSettings = size_t (*)(char* data, void* userPointer); + +using ConfigSaveNodeSettings = bool (*)(NodeId nodeId, const char* data, size_t size, SaveReasonFlags reason, void* userPointer); +using ConfigLoadNodeSettings = size_t (*)(NodeId nodeId, char* data, void* userPointer); + +using ConfigSession = void (*)(void* userPointer); + +struct Config +{ + using CanvasSizeModeAlias = ax::NodeEditor::CanvasSizeMode; + + const char* SettingsFile; + ConfigSession BeginSaveSession; + ConfigSession EndSaveSession; + ConfigSaveSettings SaveSettings; + ConfigLoadSettings LoadSettings; + ConfigSaveNodeSettings SaveNodeSettings; + ConfigLoadNodeSettings LoadNodeSettings; + void* UserPointer; + ImVector CustomZoomLevels; + CanvasSizeModeAlias CanvasSizeMode; + int DragButtonIndex; // Mouse button index drag action will react to (0-left, 1-right, 2-middle) + int SelectButtonIndex; // Mouse button index select action will react to (0-left, 1-right, 2-middle) + int NavigateButtonIndex; // Mouse button index navigate action will react to (0-left, 1-right, 2-middle) + int ContextMenuButtonIndex; // Mouse button index context menu action will react to (0-left, 1-right, 2-middle) + bool EnableSmoothZoom; + float SmoothZoomPower; + + Config() + : SettingsFile("NodeEditor.json") + , BeginSaveSession(nullptr) + , EndSaveSession(nullptr) + , SaveSettings(nullptr) + , LoadSettings(nullptr) + , SaveNodeSettings(nullptr) + , LoadNodeSettings(nullptr) + , UserPointer(nullptr) + , CustomZoomLevels() + , CanvasSizeMode(CanvasSizeModeAlias::FitVerticalView) + , DragButtonIndex(0) + , SelectButtonIndex(0) + , NavigateButtonIndex(1) + , ContextMenuButtonIndex(1) + , EnableSmoothZoom(false) +# ifdef __APPLE__ + , SmoothZoomPower(1.1f) +# else + , SmoothZoomPower(1.3f) +# endif + { + } +}; + + +//------------------------------------------------------------------------------ +enum StyleColor +{ + StyleColor_Bg, + StyleColor_Grid, + StyleColor_NodeBg, + StyleColor_NodeBorder, + StyleColor_HovNodeBorder, + StyleColor_SelNodeBorder, + StyleColor_NodeSelRect, + StyleColor_NodeSelRectBorder, + StyleColor_HovLinkBorder, + StyleColor_SelLinkBorder, + StyleColor_HighlightLinkBorder, + StyleColor_LinkSelRect, + StyleColor_LinkSelRectBorder, + StyleColor_PinRect, + StyleColor_PinRectBorder, + StyleColor_Flow, + StyleColor_FlowMarker, + StyleColor_GroupBg, + StyleColor_GroupBorder, + + StyleColor_Count +}; + +enum StyleVar +{ + StyleVar_NodePadding, + StyleVar_NodeRounding, + StyleVar_NodeBorderWidth, + StyleVar_HoveredNodeBorderWidth, + StyleVar_SelectedNodeBorderWidth, + StyleVar_PinRounding, + StyleVar_PinBorderWidth, + StyleVar_LinkStrength, + StyleVar_SourceDirection, + StyleVar_TargetDirection, + StyleVar_ScrollDuration, + StyleVar_FlowMarkerDistance, + StyleVar_FlowSpeed, + StyleVar_FlowDuration, + StyleVar_PivotAlignment, + StyleVar_PivotSize, + StyleVar_PivotScale, + StyleVar_PinCorners, + StyleVar_PinRadius, + StyleVar_PinArrowSize, + StyleVar_PinArrowWidth, + StyleVar_GroupRounding, + StyleVar_GroupBorderWidth, + StyleVar_HighlightConnectedLinks, + StyleVar_SnapLinkToPinDir, + StyleVar_HoveredNodeBorderOffset, + StyleVar_SelectedNodeBorderOffset, + + StyleVar_Count +}; + +struct Style +{ + ImVec4 NodePadding; + float NodeRounding; + float NodeBorderWidth; + float HoveredNodeBorderWidth; + float HoverNodeBorderOffset; + float SelectedNodeBorderWidth; + float SelectedNodeBorderOffset; + float PinRounding; + float PinBorderWidth; + float LinkStrength; + ImVec2 SourceDirection; + ImVec2 TargetDirection; + float ScrollDuration; + float FlowMarkerDistance; + float FlowSpeed; + float FlowDuration; + ImVec2 PivotAlignment; + ImVec2 PivotSize; + ImVec2 PivotScale; + float PinCorners; + float PinRadius; + float PinArrowSize; + float PinArrowWidth; + float GroupRounding; + float GroupBorderWidth; + float HighlightConnectedLinks; + float SnapLinkToPinDir; // when true link will start on the line defined by pin direction + ImVec4 Colors[StyleColor_Count]; + + Style() + { + NodePadding = ImVec4(8, 8, 8, 8); + NodeRounding = 12.0f; + NodeBorderWidth = 1.5f; + HoveredNodeBorderWidth = 3.5f; + HoverNodeBorderOffset = 0.0f; + SelectedNodeBorderWidth = 3.5f; + SelectedNodeBorderOffset = 0.0f; + PinRounding = 4.0f; + PinBorderWidth = 0.0f; + LinkStrength = 100.0f; + SourceDirection = ImVec2(1.0f, 0.0f); + TargetDirection = ImVec2(-1.0f, 0.0f); + ScrollDuration = 0.35f; + FlowMarkerDistance = 30.0f; + FlowSpeed = 150.0f; + FlowDuration = 2.0f; + PivotAlignment = ImVec2(0.5f, 0.5f); + PivotSize = ImVec2(0.0f, 0.0f); + PivotScale = ImVec2(1, 1); +#if IMGUI_VERSION_NUM > 18101 + PinCorners = ImDrawFlags_RoundCornersAll; +#else + PinCorners = ImDrawCornerFlags_All; +#endif + PinRadius = 0.0f; + PinArrowSize = 0.0f; + PinArrowWidth = 0.0f; + GroupRounding = 6.0f; + GroupBorderWidth = 1.0f; + HighlightConnectedLinks = 0.0f; + SnapLinkToPinDir = 0.0f; + + Colors[StyleColor_Bg] = ImColor( 60, 60, 70, 200); + Colors[StyleColor_Grid] = ImColor(120, 120, 120, 40); + Colors[StyleColor_NodeBg] = ImColor( 32, 32, 32, 200); + Colors[StyleColor_NodeBorder] = ImColor(255, 255, 255, 96); + Colors[StyleColor_HovNodeBorder] = ImColor( 50, 176, 255, 255); + Colors[StyleColor_SelNodeBorder] = ImColor(255, 176, 50, 255); + Colors[StyleColor_NodeSelRect] = ImColor( 5, 130, 255, 64); + Colors[StyleColor_NodeSelRectBorder] = ImColor( 5, 130, 255, 128); + Colors[StyleColor_HovLinkBorder] = ImColor( 50, 176, 255, 255); + Colors[StyleColor_SelLinkBorder] = ImColor(255, 176, 50, 255); + Colors[StyleColor_HighlightLinkBorder]= ImColor(204, 105, 0, 255); + Colors[StyleColor_LinkSelRect] = ImColor( 5, 130, 255, 64); + Colors[StyleColor_LinkSelRectBorder] = ImColor( 5, 130, 255, 128); + Colors[StyleColor_PinRect] = ImColor( 60, 180, 255, 100); + Colors[StyleColor_PinRectBorder] = ImColor( 60, 180, 255, 128); + Colors[StyleColor_Flow] = ImColor(255, 128, 64, 255); + Colors[StyleColor_FlowMarker] = ImColor(255, 128, 64, 255); + Colors[StyleColor_GroupBg] = ImColor( 0, 0, 0, 160); + Colors[StyleColor_GroupBorder] = ImColor(255, 255, 255, 32); + } +}; + + +//------------------------------------------------------------------------------ +struct EditorContext; + + +//------------------------------------------------------------------------------ +IMGUI_NODE_EDITOR_API void SetCurrentEditor(EditorContext* ctx); +IMGUI_NODE_EDITOR_API EditorContext* GetCurrentEditor(); +IMGUI_NODE_EDITOR_API EditorContext* CreateEditor(const Config* config = nullptr); +IMGUI_NODE_EDITOR_API void DestroyEditor(EditorContext* ctx); +IMGUI_NODE_EDITOR_API const Config& GetConfig(EditorContext* ctx = nullptr); + +IMGUI_NODE_EDITOR_API Style& GetStyle(); +IMGUI_NODE_EDITOR_API const char* GetStyleColorName(StyleColor colorIndex); + +IMGUI_NODE_EDITOR_API void PushStyleColor(StyleColor colorIndex, const ImVec4& color); +IMGUI_NODE_EDITOR_API void PopStyleColor(int count = 1); + +IMGUI_NODE_EDITOR_API void PushStyleVar(StyleVar varIndex, float value); +IMGUI_NODE_EDITOR_API void PushStyleVar(StyleVar varIndex, const ImVec2& value); +IMGUI_NODE_EDITOR_API void PushStyleVar(StyleVar varIndex, const ImVec4& value); +IMGUI_NODE_EDITOR_API void PopStyleVar(int count = 1); + +IMGUI_NODE_EDITOR_API void Begin(const char* id, const ImVec2& size = ImVec2(0, 0)); +IMGUI_NODE_EDITOR_API void End(); + +IMGUI_NODE_EDITOR_API void BeginNode(NodeId id); +IMGUI_NODE_EDITOR_API void BeginPin(PinId id, PinKind kind); +IMGUI_NODE_EDITOR_API void PinRect(const ImVec2& a, const ImVec2& b); +IMGUI_NODE_EDITOR_API void PinPivotRect(const ImVec2& a, const ImVec2& b); +IMGUI_NODE_EDITOR_API void PinPivotSize(const ImVec2& size); +IMGUI_NODE_EDITOR_API void PinPivotScale(const ImVec2& scale); +IMGUI_NODE_EDITOR_API void PinPivotAlignment(const ImVec2& alignment); +IMGUI_NODE_EDITOR_API void EndPin(); +IMGUI_NODE_EDITOR_API void Group(const ImVec2& size); +IMGUI_NODE_EDITOR_API void EndNode(); + +IMGUI_NODE_EDITOR_API bool BeginGroupHint(NodeId nodeId); +IMGUI_NODE_EDITOR_API ImVec2 GetGroupMin(); +IMGUI_NODE_EDITOR_API ImVec2 GetGroupMax(); +IMGUI_NODE_EDITOR_API ImDrawList* GetHintForegroundDrawList(); +IMGUI_NODE_EDITOR_API ImDrawList* GetHintBackgroundDrawList(); +IMGUI_NODE_EDITOR_API void EndGroupHint(); + +// TODO: Add a way to manage node background channels +IMGUI_NODE_EDITOR_API ImDrawList* GetNodeBackgroundDrawList(NodeId nodeId); + +IMGUI_NODE_EDITOR_API bool Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f); + +IMGUI_NODE_EDITOR_API void Flow(LinkId linkId, FlowDirection direction = FlowDirection::Forward); + +IMGUI_NODE_EDITOR_API bool BeginCreate(const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f); +IMGUI_NODE_EDITOR_API bool QueryNewLink(PinId* startId, PinId* endId); +IMGUI_NODE_EDITOR_API bool QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness = 1.0f); +IMGUI_NODE_EDITOR_API bool QueryNewNode(PinId* pinId); +IMGUI_NODE_EDITOR_API bool QueryNewNode(PinId* pinId, const ImVec4& color, float thickness = 1.0f); +IMGUI_NODE_EDITOR_API bool AcceptNewItem(); +IMGUI_NODE_EDITOR_API bool AcceptNewItem(const ImVec4& color, float thickness = 1.0f); +IMGUI_NODE_EDITOR_API void RejectNewItem(); +IMGUI_NODE_EDITOR_API void RejectNewItem(const ImVec4& color, float thickness = 1.0f); +IMGUI_NODE_EDITOR_API void EndCreate(); + +IMGUI_NODE_EDITOR_API bool BeginDelete(); +IMGUI_NODE_EDITOR_API bool QueryDeletedLink(LinkId* linkId, PinId* startId = nullptr, PinId* endId = nullptr); +IMGUI_NODE_EDITOR_API bool QueryDeletedNode(NodeId* nodeId); +IMGUI_NODE_EDITOR_API bool AcceptDeletedItem(bool deleteDependencies = true); +IMGUI_NODE_EDITOR_API void RejectDeletedItem(); +IMGUI_NODE_EDITOR_API void EndDelete(); + +IMGUI_NODE_EDITOR_API void SetNodePosition(NodeId nodeId, const ImVec2& editorPosition); +IMGUI_NODE_EDITOR_API void SetGroupSize(NodeId nodeId, const ImVec2& size); +IMGUI_NODE_EDITOR_API ImVec2 GetNodePosition(NodeId nodeId); +IMGUI_NODE_EDITOR_API ImVec2 GetNodeSize(NodeId nodeId); +IMGUI_NODE_EDITOR_API void CenterNodeOnScreen(NodeId nodeId); +IMGUI_NODE_EDITOR_API void SetNodeZPosition(NodeId nodeId, float z); // Sets node z position, nodes with higher value are drawn over nodes with lower value +IMGUI_NODE_EDITOR_API float GetNodeZPosition(NodeId nodeId); // Returns node z position, defaults is 0.0f + +IMGUI_NODE_EDITOR_API void RestoreNodeState(NodeId nodeId); + +IMGUI_NODE_EDITOR_API void Suspend(); +IMGUI_NODE_EDITOR_API void Resume(); +IMGUI_NODE_EDITOR_API bool IsSuspended(); + +IMGUI_NODE_EDITOR_API bool IsActive(); + +IMGUI_NODE_EDITOR_API bool HasSelectionChanged(); +IMGUI_NODE_EDITOR_API int GetSelectedObjectCount(); +IMGUI_NODE_EDITOR_API int GetSelectedNodes(NodeId* nodes, int size); +IMGUI_NODE_EDITOR_API int GetSelectedLinks(LinkId* links, int size); +IMGUI_NODE_EDITOR_API bool IsNodeSelected(NodeId nodeId); +IMGUI_NODE_EDITOR_API bool IsLinkSelected(LinkId linkId); +IMGUI_NODE_EDITOR_API void ClearSelection(); +IMGUI_NODE_EDITOR_API void SelectNode(NodeId nodeId, bool append = false); +IMGUI_NODE_EDITOR_API void SelectLink(LinkId linkId, bool append = false); +IMGUI_NODE_EDITOR_API void DeselectNode(NodeId nodeId); +IMGUI_NODE_EDITOR_API void DeselectLink(LinkId linkId); + +IMGUI_NODE_EDITOR_API bool DeleteNode(NodeId nodeId); +IMGUI_NODE_EDITOR_API bool DeleteLink(LinkId linkId); + +IMGUI_NODE_EDITOR_API bool HasAnyLinks(NodeId nodeId); // Returns true if node has any link connected +IMGUI_NODE_EDITOR_API bool HasAnyLinks(PinId pinId); // Return true if pin has any link connected +IMGUI_NODE_EDITOR_API int BreakLinks(NodeId nodeId); // Break all links connected to this node +IMGUI_NODE_EDITOR_API int BreakLinks(PinId pinId); // Break all links connected to this pin + +IMGUI_NODE_EDITOR_API void NavigateToContent(float duration = -1); +IMGUI_NODE_EDITOR_API void NavigateToSelection(bool zoomIn = false, float duration = -1); + +IMGUI_NODE_EDITOR_API bool ShowNodeContextMenu(NodeId* nodeId); +IMGUI_NODE_EDITOR_API bool ShowPinContextMenu(PinId* pinId); +IMGUI_NODE_EDITOR_API bool ShowLinkContextMenu(LinkId* linkId); +IMGUI_NODE_EDITOR_API bool ShowBackgroundContextMenu(); + +IMGUI_NODE_EDITOR_API void EnableShortcuts(bool enable); +IMGUI_NODE_EDITOR_API bool AreShortcutsEnabled(); + +IMGUI_NODE_EDITOR_API bool BeginShortcut(); +IMGUI_NODE_EDITOR_API bool AcceptCut(); +IMGUI_NODE_EDITOR_API bool AcceptCopy(); +IMGUI_NODE_EDITOR_API bool AcceptPaste(); +IMGUI_NODE_EDITOR_API bool AcceptDuplicate(); +IMGUI_NODE_EDITOR_API bool AcceptCreateNode(); +IMGUI_NODE_EDITOR_API int GetActionContextSize(); +IMGUI_NODE_EDITOR_API int GetActionContextNodes(NodeId* nodes, int size); +IMGUI_NODE_EDITOR_API int GetActionContextLinks(LinkId* links, int size); +IMGUI_NODE_EDITOR_API void EndShortcut(); + +IMGUI_NODE_EDITOR_API float GetCurrentZoom(); + +IMGUI_NODE_EDITOR_API NodeId GetHoveredNode(); +IMGUI_NODE_EDITOR_API PinId GetHoveredPin(); +IMGUI_NODE_EDITOR_API LinkId GetHoveredLink(); +IMGUI_NODE_EDITOR_API NodeId GetDoubleClickedNode(); +IMGUI_NODE_EDITOR_API PinId GetDoubleClickedPin(); +IMGUI_NODE_EDITOR_API LinkId GetDoubleClickedLink(); +IMGUI_NODE_EDITOR_API bool IsBackgroundClicked(); +IMGUI_NODE_EDITOR_API bool IsBackgroundDoubleClicked(); +IMGUI_NODE_EDITOR_API ImGuiMouseButton GetBackgroundClickButtonIndex(); // -1 if none +IMGUI_NODE_EDITOR_API ImGuiMouseButton GetBackgroundDoubleClickButtonIndex(); // -1 if none + +IMGUI_NODE_EDITOR_API bool GetLinkPins(LinkId linkId, PinId* startPinId, PinId* endPinId); // pass nullptr if particular pin do not interest you + +IMGUI_NODE_EDITOR_API bool PinHadAnyLinks(PinId pinId); + +IMGUI_NODE_EDITOR_API ImVec2 GetScreenSize(); +IMGUI_NODE_EDITOR_API ImVec2 ScreenToCanvas(const ImVec2& pos); +IMGUI_NODE_EDITOR_API ImVec2 CanvasToScreen(const ImVec2& pos); + +IMGUI_NODE_EDITOR_API int GetNodeCount(); // Returns number of submitted nodes since Begin() call +IMGUI_NODE_EDITOR_API int GetOrderedNodeIds(NodeId* nodes, int size); // Fills an array with node id's in order they're drawn; up to 'size` elements are set. Returns actual size of filled id's. + + + + + + + +//------------------------------------------------------------------------------ +namespace Details { + +template +struct SafeType +{ + SafeType(T t) + : m_Value(std::move(t)) + { + } + + SafeType(const SafeType&) = default; + + template + SafeType( + const SafeType + < + typename std::enable_if::value, T2>::type, + typename std::enable_if::value, Tag2>::type + >&) = delete; + + SafeType& operator=(const SafeType&) = default; + + explicit operator T() const { return Get(); } + + T Get() const { return m_Value; } + +private: + T m_Value; +}; + + +template +struct SafePointerType + : SafeType +{ + static const Tag Invalid; + + using SafeType::SafeType; + + SafePointerType() + : SafePointerType(Invalid) + { + } + + template explicit SafePointerType(T* ptr): SafePointerType(reinterpret_cast(ptr)) {} + template T* AsPointer() const { return reinterpret_cast(this->Get()); } + + explicit operator bool() const { return *this != Invalid; } +}; + +template +const Tag SafePointerType::Invalid = { 0 }; + +template +inline bool operator==(const SafePointerType& lhs, const SafePointerType& rhs) +{ + return lhs.Get() == rhs.Get(); +} + +template +inline bool operator!=(const SafePointerType& lhs, const SafePointerType& rhs) +{ + return lhs.Get() != rhs.Get(); +} + +} // namespace Details + +struct NodeId final: Details::SafePointerType +{ + using SafePointerType::SafePointerType; +}; + +struct LinkId final: Details::SafePointerType +{ + using SafePointerType::SafePointerType; +}; + +struct PinId final: Details::SafePointerType +{ + using SafePointerType::SafePointerType; +}; + + +//------------------------------------------------------------------------------ +} // namespace Editor +} // namespace ax + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_NODE_EDITOR_H__ diff --git a/cpp/vendor/imgui-node-editor/imgui_node_editor_api.cpp b/cpp/vendor/imgui-node-editor/imgui_node_editor_api.cpp new file mode 100644 index 00000000..c8c7c3ff --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_node_editor_api.cpp @@ -0,0 +1,762 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# include "imgui_node_editor_internal.h" +# include + + +//------------------------------------------------------------------------------ +static ax::NodeEditor::Detail::EditorContext* s_Editor = nullptr; + + +//------------------------------------------------------------------------------ +template +static int BuildIdList(C& container, I* list, int listSize, F&& accept) +{ + if (list != nullptr) + { + int count = 0; + for (auto object : container) + { + if (listSize <= 0) + break; + + if (accept(object)) + { + list[count] = I(object->ID().AsPointer()); + ++count; + --listSize;} + } + + return count; + } + else + return static_cast(std::count_if(container.begin(), container.end(), accept)); +} + + +//------------------------------------------------------------------------------ +ax::NodeEditor::EditorContext* ax::NodeEditor::CreateEditor(const Config* config) +{ + return reinterpret_cast(new ax::NodeEditor::Detail::EditorContext(config)); +} + +void ax::NodeEditor::DestroyEditor(EditorContext* ctx) +{ + auto lastContext = GetCurrentEditor(); + + // Set context we're about to destroy as current, to give callback valid context + if (lastContext != ctx) + SetCurrentEditor(ctx); + + auto editor = reinterpret_cast(ctx); + + delete editor; + + if (lastContext != ctx) + SetCurrentEditor(lastContext); +} + +const ax::NodeEditor::Config& ax::NodeEditor::GetConfig(EditorContext* ctx) +{ + if (ctx == nullptr) + ctx = GetCurrentEditor(); + + if (ctx) + { + auto editor = reinterpret_cast(ctx); + + return editor->GetConfig(); + } + else + { + static Config s_EmptyConfig; + return s_EmptyConfig; + } +} + +void ax::NodeEditor::SetCurrentEditor(EditorContext* ctx) +{ + s_Editor = reinterpret_cast(ctx); +} + +ax::NodeEditor::EditorContext* ax::NodeEditor::GetCurrentEditor() +{ + return reinterpret_cast(s_Editor); +} + +ax::NodeEditor::Style& ax::NodeEditor::GetStyle() +{ + return s_Editor->GetStyle(); +} + +const char* ax::NodeEditor::GetStyleColorName(StyleColor colorIndex) +{ + return s_Editor->GetStyle().GetColorName(colorIndex); +} + +void ax::NodeEditor::PushStyleColor(StyleColor colorIndex, const ImVec4& color) +{ + s_Editor->GetStyle().PushColor(colorIndex, color); +} + +void ax::NodeEditor::PopStyleColor(int count) +{ + s_Editor->GetStyle().PopColor(count); +} + +void ax::NodeEditor::PushStyleVar(StyleVar varIndex, float value) +{ + s_Editor->GetStyle().PushVar(varIndex, value); +} + +void ax::NodeEditor::PushStyleVar(StyleVar varIndex, const ImVec2& value) +{ + s_Editor->GetStyle().PushVar(varIndex, value); +} + +void ax::NodeEditor::PushStyleVar(StyleVar varIndex, const ImVec4& value) +{ + s_Editor->GetStyle().PushVar(varIndex, value); +} + +void ax::NodeEditor::PopStyleVar(int count) +{ + s_Editor->GetStyle().PopVar(count); +} + +void ax::NodeEditor::Begin(const char* id, const ImVec2& size) +{ + s_Editor->Begin(id, size); +} + +void ax::NodeEditor::End() +{ + s_Editor->End(); +} + +void ax::NodeEditor::BeginNode(NodeId id) +{ + s_Editor->GetNodeBuilder().Begin(id); +} + +void ax::NodeEditor::BeginPin(PinId id, PinKind kind) +{ + s_Editor->GetNodeBuilder().BeginPin(id, kind); +} + +void ax::NodeEditor::PinRect(const ImVec2& a, const ImVec2& b) +{ + s_Editor->GetNodeBuilder().PinRect(a, b); +} + +void ax::NodeEditor::PinPivotRect(const ImVec2& a, const ImVec2& b) +{ + s_Editor->GetNodeBuilder().PinPivotRect(a, b); +} + +void ax::NodeEditor::PinPivotSize(const ImVec2& size) +{ + s_Editor->GetNodeBuilder().PinPivotSize(size); +} + +void ax::NodeEditor::PinPivotScale(const ImVec2& scale) +{ + s_Editor->GetNodeBuilder().PinPivotScale(scale); +} + +void ax::NodeEditor::PinPivotAlignment(const ImVec2& alignment) +{ + s_Editor->GetNodeBuilder().PinPivotAlignment(alignment); +} + +void ax::NodeEditor::EndPin() +{ + s_Editor->GetNodeBuilder().EndPin(); +} + +void ax::NodeEditor::Group(const ImVec2& size) +{ + s_Editor->GetNodeBuilder().Group(size); +} + +void ax::NodeEditor::EndNode() +{ + s_Editor->GetNodeBuilder().End(); +} + +bool ax::NodeEditor::BeginGroupHint(NodeId nodeId) +{ + return s_Editor->GetHintBuilder().Begin(nodeId); +} + +ImVec2 ax::NodeEditor::GetGroupMin() +{ + return s_Editor->GetHintBuilder().GetGroupMin(); +} + +ImVec2 ax::NodeEditor::GetGroupMax() +{ + return s_Editor->GetHintBuilder().GetGroupMax(); +} + +ImDrawList* ax::NodeEditor::GetHintForegroundDrawList() +{ + return s_Editor->GetHintBuilder().GetForegroundDrawList(); +} + +ImDrawList* ax::NodeEditor::GetHintBackgroundDrawList() +{ + return s_Editor->GetHintBuilder().GetBackgroundDrawList(); +} + +void ax::NodeEditor::EndGroupHint() +{ + s_Editor->GetHintBuilder().End(); +} + +ImDrawList* ax::NodeEditor::GetNodeBackgroundDrawList(NodeId nodeId) +{ + if (auto node = s_Editor->FindNode(nodeId)) + return s_Editor->GetNodeBuilder().GetUserBackgroundDrawList(node); + else + return nullptr; +} + +bool ax::NodeEditor::Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, float thickness/* = 1.0f*/) +{ + return s_Editor->DoLink(id, startPinId, endPinId, ImColor(color), thickness); +} + +void ax::NodeEditor::Flow(LinkId linkId, FlowDirection direction) +{ + if (auto link = s_Editor->FindLink(linkId)) + s_Editor->Flow(link, direction); +} + +bool ax::NodeEditor::BeginCreate(const ImVec4& color, float thickness) +{ + auto& context = s_Editor->GetItemCreator(); + + if (context.Begin()) + { + context.SetStyle(ImColor(color), thickness); + return true; + } + else + return false; +} + +bool ax::NodeEditor::QueryNewLink(PinId* startId, PinId* endId) +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + return context.QueryLink(startId, endId) == Result::True; +} + +bool ax::NodeEditor::QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness) +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + auto result = context.QueryLink(startId, endId); + if (result != Result::Indeterminate) + context.SetStyle(ImColor(color), thickness); + + return result == Result::True; +} + +bool ax::NodeEditor::QueryNewNode(PinId* pinId) +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + return context.QueryNode(pinId) == Result::True; +} + +bool ax::NodeEditor::QueryNewNode(PinId* pinId, const ImVec4& color, float thickness) +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + auto result = context.QueryNode(pinId); + if (result != Result::Indeterminate) + context.SetStyle(ImColor(color), thickness); + + return result == Result::True; +} + +bool ax::NodeEditor::AcceptNewItem() +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + return context.AcceptItem() == Result::True; +} + +bool ax::NodeEditor::AcceptNewItem(const ImVec4& color, float thickness) +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + auto result = context.AcceptItem(); + if (result != Result::Indeterminate) + context.SetStyle(ImColor(color), thickness); + + return result == Result::True; +} + +void ax::NodeEditor::RejectNewItem() +{ + auto& context = s_Editor->GetItemCreator(); + + context.RejectItem(); +} + +void ax::NodeEditor::RejectNewItem(const ImVec4& color, float thickness) +{ + using Result = ax::NodeEditor::Detail::CreateItemAction::Result; + + auto& context = s_Editor->GetItemCreator(); + + if (context.RejectItem() != Result::Indeterminate) + context.SetStyle(ImColor(color), thickness); +} + +void ax::NodeEditor::EndCreate() +{ + auto& context = s_Editor->GetItemCreator(); + + context.End(); +} + +bool ax::NodeEditor::BeginDelete() +{ + auto& context = s_Editor->GetItemDeleter(); + + return context.Begin(); +} + +bool ax::NodeEditor::QueryDeletedLink(LinkId* linkId, PinId* startId, PinId* endId) +{ + auto& context = s_Editor->GetItemDeleter(); + + return context.QueryLink(linkId, startId, endId); +} + +bool ax::NodeEditor::QueryDeletedNode(NodeId* nodeId) +{ + auto& context = s_Editor->GetItemDeleter(); + + return context.QueryNode(nodeId); +} + +bool ax::NodeEditor::AcceptDeletedItem(bool deleteDependencies) +{ + auto& context = s_Editor->GetItemDeleter(); + + return context.AcceptItem(deleteDependencies); +} + +void ax::NodeEditor::RejectDeletedItem() +{ + auto& context = s_Editor->GetItemDeleter(); + + context.RejectItem(); +} + +void ax::NodeEditor::EndDelete() +{ + auto& context = s_Editor->GetItemDeleter(); + + context.End(); +} + +void ax::NodeEditor::SetNodePosition(NodeId nodeId, const ImVec2& position) +{ + s_Editor->SetNodePosition(nodeId, position); +} + +void ax::NodeEditor::SetGroupSize(NodeId nodeId, const ImVec2& size) +{ + s_Editor->SetGroupSize(nodeId, size); +} + +ImVec2 ax::NodeEditor::GetNodePosition(NodeId nodeId) +{ + return s_Editor->GetNodePosition(nodeId); +} + +ImVec2 ax::NodeEditor::GetNodeSize(NodeId nodeId) +{ + return s_Editor->GetNodeSize(nodeId); +} + +void ax::NodeEditor::CenterNodeOnScreen(NodeId nodeId) +{ + if (auto node = s_Editor->FindNode(nodeId)) + node->CenterOnScreenInNextFrame(); +} + +void ax::NodeEditor::SetNodeZPosition(NodeId nodeId, float z) +{ + s_Editor->SetNodeZPosition(nodeId, z); +} + +float ax::NodeEditor::GetNodeZPosition(NodeId nodeId) +{ + return s_Editor->GetNodeZPosition(nodeId); +} + +void ax::NodeEditor::RestoreNodeState(NodeId nodeId) +{ + if (auto node = s_Editor->FindNode(nodeId)) + s_Editor->MarkNodeToRestoreState(node); +} + +void ax::NodeEditor::Suspend() +{ + s_Editor->Suspend(); +} + +void ax::NodeEditor::Resume() +{ + s_Editor->Resume(); +} + +bool ax::NodeEditor::IsSuspended() +{ + return s_Editor->IsSuspended(); +} + +bool ax::NodeEditor::IsActive() +{ + return s_Editor->IsFocused(); +} + +bool ax::NodeEditor::HasSelectionChanged() +{ + return s_Editor->HasSelectionChanged(); +} + +int ax::NodeEditor::GetSelectedObjectCount() +{ + return (int)s_Editor->GetSelectedObjects().size(); +} + +int ax::NodeEditor::GetSelectedNodes(NodeId* nodes, int size) +{ + return BuildIdList(s_Editor->GetSelectedObjects(), nodes, size, [](auto object) + { + return object->AsNode() != nullptr; + }); +} + +int ax::NodeEditor::GetSelectedLinks(LinkId* links, int size) +{ + return BuildIdList(s_Editor->GetSelectedObjects(), links, size, [](auto object) + { + return object->AsLink() != nullptr; + }); +} + +bool ax::NodeEditor::IsNodeSelected(NodeId nodeId) +{ + if (auto node = s_Editor->FindNode(nodeId)) + return s_Editor->IsSelected(node); + else + return false; +} + +bool ax::NodeEditor::IsLinkSelected(LinkId linkId) +{ + if (auto link = s_Editor->FindLink(linkId)) + return s_Editor->IsSelected(link); + else + return false; +} + +void ax::NodeEditor::ClearSelection() +{ + s_Editor->ClearSelection(); +} + +void ax::NodeEditor::SelectNode(NodeId nodeId, bool append) +{ + if (auto node = s_Editor->FindNode(nodeId)) + { + if (append) + s_Editor->SelectObject(node); + else + s_Editor->SetSelectedObject(node); + } +} + +void ax::NodeEditor::SelectLink(LinkId linkId, bool append) +{ + if (auto link = s_Editor->FindLink(linkId)) + { + if (append) + s_Editor->SelectObject(link); + else + s_Editor->SetSelectedObject(link); + } +} + +void ax::NodeEditor::DeselectNode(NodeId nodeId) +{ + if (auto node = s_Editor->FindNode(nodeId)) + s_Editor->DeselectObject(node); +} + +void ax::NodeEditor::DeselectLink(LinkId linkId) +{ + if (auto link = s_Editor->FindLink(linkId)) + s_Editor->DeselectObject(link); +} + +bool ax::NodeEditor::DeleteNode(NodeId nodeId) +{ + if (auto node = s_Editor->FindNode(nodeId)) + return s_Editor->GetItemDeleter().Add(node); + else + return false; +} + +bool ax::NodeEditor::DeleteLink(LinkId linkId) +{ + if (auto link = s_Editor->FindLink(linkId)) + return s_Editor->GetItemDeleter().Add(link); + else + return false; +} + +bool ax::NodeEditor::HasAnyLinks(NodeId nodeId) +{ + return s_Editor->HasAnyLinks(nodeId); +} + +bool ax::NodeEditor::HasAnyLinks(PinId pinId) +{ + return s_Editor->HasAnyLinks(pinId); +} + +int ax::NodeEditor::BreakLinks(NodeId nodeId) +{ + return s_Editor->BreakLinks(nodeId); +} + +int ax::NodeEditor::BreakLinks(PinId pinId) +{ + return s_Editor->BreakLinks(pinId); +} + +void ax::NodeEditor::NavigateToContent(float duration) +{ + s_Editor->NavigateTo(s_Editor->GetContentBounds(), true, duration); +} + +void ax::NodeEditor::NavigateToSelection(bool zoomIn, float duration) +{ + s_Editor->NavigateTo(s_Editor->GetSelectionBounds(), zoomIn, duration); +} + +bool ax::NodeEditor::ShowNodeContextMenu(NodeId* nodeId) +{ + return s_Editor->GetContextMenu().ShowNodeContextMenu(nodeId); +} + +bool ax::NodeEditor::ShowPinContextMenu(PinId* pinId) +{ + return s_Editor->GetContextMenu().ShowPinContextMenu(pinId); +} + +bool ax::NodeEditor::ShowLinkContextMenu(LinkId* linkId) +{ + return s_Editor->GetContextMenu().ShowLinkContextMenu(linkId); +} + +bool ax::NodeEditor::ShowBackgroundContextMenu() +{ + return s_Editor->GetContextMenu().ShowBackgroundContextMenu(); +} + +void ax::NodeEditor::EnableShortcuts(bool enable) +{ + s_Editor->EnableShortcuts(enable); +} + +bool ax::NodeEditor::AreShortcutsEnabled() +{ + return s_Editor->AreShortcutsEnabled(); +} + +bool ax::NodeEditor::BeginShortcut() +{ + return s_Editor->GetShortcut().Begin(); +} + +bool ax::NodeEditor::AcceptCut() +{ + return s_Editor->GetShortcut().AcceptCut(); +} + +bool ax::NodeEditor::AcceptCopy() +{ + return s_Editor->GetShortcut().AcceptCopy(); +} + +bool ax::NodeEditor::AcceptPaste() +{ + return s_Editor->GetShortcut().AcceptPaste(); +} + +bool ax::NodeEditor::AcceptDuplicate() +{ + return s_Editor->GetShortcut().AcceptDuplicate(); +} + +bool ax::NodeEditor::AcceptCreateNode() +{ + return s_Editor->GetShortcut().AcceptCreateNode(); +} + +int ax::NodeEditor::GetActionContextSize() +{ + return static_cast(s_Editor->GetShortcut().m_Context.size()); +} + +int ax::NodeEditor::GetActionContextNodes(NodeId* nodes, int size) +{ + return BuildIdList(s_Editor->GetSelectedObjects(), nodes, size, [](auto object) + { + return object->AsNode() != nullptr; + }); +} + +int ax::NodeEditor::GetActionContextLinks(LinkId* links, int size) +{ + return BuildIdList(s_Editor->GetSelectedObjects(), links, size, [](auto object) + { + return object->AsLink() != nullptr; + }); +} + +void ax::NodeEditor::EndShortcut() +{ + return s_Editor->GetShortcut().End(); +} + +float ax::NodeEditor::GetCurrentZoom() +{ + return s_Editor->GetView().InvScale; +} + +ax::NodeEditor::NodeId ax::NodeEditor::GetHoveredNode() +{ + return s_Editor->GetHoveredNode(); +} + +ax::NodeEditor::PinId ax::NodeEditor::GetHoveredPin() +{ + return s_Editor->GetHoveredPin(); +} + +ax::NodeEditor::LinkId ax::NodeEditor::GetHoveredLink() +{ + return s_Editor->GetHoveredLink(); +} + +ax::NodeEditor::NodeId ax::NodeEditor::GetDoubleClickedNode() +{ + return s_Editor->GetDoubleClickedNode(); +} + +ax::NodeEditor::PinId ax::NodeEditor::GetDoubleClickedPin() +{ + return s_Editor->GetDoubleClickedPin(); +} + +ax::NodeEditor::LinkId ax::NodeEditor::GetDoubleClickedLink() +{ + return s_Editor->GetDoubleClickedLink(); +} + +bool ax::NodeEditor::IsBackgroundClicked() +{ + return s_Editor->IsBackgroundClicked(); +} + +bool ax::NodeEditor::IsBackgroundDoubleClicked() +{ + return s_Editor->IsBackgroundDoubleClicked(); +} + +ImGuiMouseButton ax::NodeEditor::GetBackgroundClickButtonIndex() +{ + return s_Editor->GetBackgroundClickButtonIndex(); +} + +ImGuiMouseButton ax::NodeEditor::GetBackgroundDoubleClickButtonIndex() +{ + return s_Editor->GetBackgroundDoubleClickButtonIndex(); +} + +bool ax::NodeEditor::GetLinkPins(LinkId linkId, PinId* startPinId, PinId* endPinId) +{ + auto link = s_Editor->FindLink(linkId); + if (!link) + return false; + + if (startPinId) + *startPinId = link->m_StartPin->m_ID; + if (endPinId) + *endPinId = link->m_EndPin->m_ID; + + return true; +} + +bool ax::NodeEditor::PinHadAnyLinks(PinId pinId) +{ + return s_Editor->PinHadAnyLinks(pinId); +} + +ImVec2 ax::NodeEditor::GetScreenSize() +{ + return s_Editor->GetRect().GetSize(); +} + +ImVec2 ax::NodeEditor::ScreenToCanvas(const ImVec2& pos) +{ + return s_Editor->ToCanvas(pos); +} + +ImVec2 ax::NodeEditor::CanvasToScreen(const ImVec2& pos) +{ + return s_Editor->ToScreen(pos); +} + +int ax::NodeEditor::GetNodeCount() +{ + return s_Editor->CountLiveNodes(); +} + +int ax::NodeEditor::GetOrderedNodeIds(NodeId* nodes, int size) +{ + return s_Editor->GetNodeIds(nodes, size); +} diff --git a/cpp/vendor/imgui-node-editor/imgui_node_editor_internal.h b/cpp/vendor/imgui-node-editor/imgui_node_editor_internal.h new file mode 100644 index 00000000..0d018cf5 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_node_editor_internal.h @@ -0,0 +1,1560 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_NODE_EDITOR_INTERNAL_H__ +# define __IMGUI_NODE_EDITOR_INTERNAL_H__ +# pragma once + + +//------------------------------------------------------------------------------ +# ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +# endif +# include "imgui_node_editor.h" + + +//------------------------------------------------------------------------------ +# include +# include +# include "imgui_extra_math.h" +# include "imgui_bezier_math.h" +# include "imgui_canvas.h" + +# include "crude_json.h" + +# include +# include + + +//------------------------------------------------------------------------------ +namespace ax { +namespace NodeEditor { +namespace Detail { + + +//------------------------------------------------------------------------------ +namespace ed = ax::NodeEditor::Detail; +namespace json = crude_json; + + +//------------------------------------------------------------------------------ +using std::vector; +using std::string; + + +//------------------------------------------------------------------------------ +void Log(const char* fmt, ...); + + +//------------------------------------------------------------------------------ +//inline ImRect ToRect(const ax::rectf& rect); +//inline ImRect ToRect(const ax::rect& rect); +inline ImRect ImGui_GetItemRect(); +inline ImVec2 ImGui_GetMouseClickPos(ImGuiMouseButton buttonIndex); + + +//------------------------------------------------------------------------------ +// https://stackoverflow.com/a/36079786 +# define DECLARE_HAS_MEMBER(__trait_name__, __member_name__) \ + \ + template \ + class __trait_name__ \ + { \ + using check_type = ::std::remove_const_t<__boost_has_member_T__>; \ + struct no_type {char x[2];}; \ + using yes_type = char; \ + \ + struct base { void __member_name__() {}}; \ + struct mixin : public base, public check_type {}; \ + \ + template struct aux {}; \ + \ + template static no_type test(aux<&U::__member_name__>*); \ + template static yes_type test(...); \ + \ + public: \ + \ + static constexpr bool value = (sizeof(yes_type) == sizeof(test(0))); \ + } + +DECLARE_HAS_MEMBER(HasFringeScale, _FringeScale); + +# undef DECLARE_HAS_MEMBER + +struct FringeScaleRef +{ + // Overload is present when ImDrawList does have _FringeScale member variable. + template + static float& Get(typename std::enable_if::value, T>::type* drawList) + { + return drawList->_FringeScale; + } + + // Overload is present when ImDrawList does not have _FringeScale member variable. + template + static float& Get(typename std::enable_if::value, T>::type*) + { + static float placeholder = 1.0f; + return placeholder; + } +}; + +static inline float& ImFringeScaleRef(ImDrawList* drawList) +{ + return FringeScaleRef::Get(drawList); +} + +struct FringeScaleScope +{ + + FringeScaleScope(float scale) + : m_LastFringeScale(ImFringeScaleRef(ImGui::GetWindowDrawList())) + { + ImFringeScaleRef(ImGui::GetWindowDrawList()) = scale; + } + + ~FringeScaleScope() + { + ImFringeScaleRef(ImGui::GetWindowDrawList()) = m_LastFringeScale; + } + +private: + float m_LastFringeScale; +}; + + +//------------------------------------------------------------------------------ +enum class ObjectType +{ + None, + Node, + Link, + Pin +}; + +using ax::NodeEditor::PinKind; +using ax::NodeEditor::StyleColor; +using ax::NodeEditor::StyleVar; +using ax::NodeEditor::SaveReasonFlags; + +using ax::NodeEditor::NodeId; +using ax::NodeEditor::PinId; +using ax::NodeEditor::LinkId; + +struct ObjectId final: Details::SafePointerType +{ + using Super = Details::SafePointerType; + using Super::Super; + + ObjectId(): Super(Invalid), m_Type(ObjectType::None) {} + ObjectId(PinId pinId): Super(pinId.AsPointer()), m_Type(ObjectType::Pin) {} + ObjectId(NodeId nodeId): Super(nodeId.AsPointer()), m_Type(ObjectType::Node) {} + ObjectId(LinkId linkId): Super(linkId.AsPointer()), m_Type(ObjectType::Link) {} + + explicit operator PinId() const { return AsPinId(); } + explicit operator NodeId() const { return AsNodeId(); } + explicit operator LinkId() const { return AsLinkId(); } + + PinId AsPinId() const { IM_ASSERT(IsPinId()); return PinId(AsPointer()); } + NodeId AsNodeId() const { IM_ASSERT(IsNodeId()); return NodeId(AsPointer()); } + LinkId AsLinkId() const { IM_ASSERT(IsLinkId()); return LinkId(AsPointer()); } + + bool IsPinId() const { return m_Type == ObjectType::Pin; } + bool IsNodeId() const { return m_Type == ObjectType::Node; } + bool IsLinkId() const { return m_Type == ObjectType::Link; } + + ObjectType Type() const { return m_Type; } + +private: + ObjectType m_Type; +}; + +struct EditorContext; + +struct Node; +struct Pin; +struct Link; + +template +struct ObjectWrapper +{ + Id m_ID; + T* m_Object; + + T* operator->() { return m_Object; } + const T* operator->() const { return m_Object; } + + operator T*() { return m_Object; } + operator const T*() const { return m_Object; } + + bool operator<(const ObjectWrapper& rhs) const + { + return m_ID.AsPointer() < rhs.m_ID.AsPointer(); + } +}; + +struct Object +{ + enum DrawFlags + { + None = 0, + Hovered = 1, + Selected = 2, + Highlighted = 4, + }; + + inline friend DrawFlags operator|(DrawFlags lhs, DrawFlags rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } + inline friend DrawFlags operator&(DrawFlags lhs, DrawFlags rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } + inline friend DrawFlags& operator|=(DrawFlags& lhs, DrawFlags rhs) { lhs = lhs | rhs; return lhs; } + inline friend DrawFlags& operator&=(DrawFlags& lhs, DrawFlags rhs) { lhs = lhs & rhs; return lhs; } + + EditorContext* const Editor; + + bool m_IsLive; + bool m_IsSelected; + bool m_DeleteOnNewFrame; + + Object(EditorContext* editor) + : Editor(editor) + , m_IsLive(true) + , m_IsSelected(false) + , m_DeleteOnNewFrame(false) + { + } + + virtual ~Object() = default; + + virtual ObjectId ID() = 0; + + bool IsVisible() const + { + if (!m_IsLive) + return false; + + const auto bounds = GetBounds(); + + return ImGui::IsRectVisible(bounds.Min, bounds.Max); + } + + virtual void Reset() { m_IsLive = false; } + + virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) = 0; + + virtual bool AcceptDrag() { return false; } + virtual void UpdateDrag(const ImVec2& offset) { IM_UNUSED(offset); } + virtual bool EndDrag() { return false; } + virtual ImVec2 DragStartLocation() { return GetBounds().Min; } + + virtual bool IsDraggable() { bool result = AcceptDrag(); EndDrag(); return result; } + virtual bool IsSelectable() { return false; } + + virtual bool TestHit(const ImVec2& point, float extraThickness = 0.0f) const + { + if (!m_IsLive) + return false; + + auto bounds = GetBounds(); + if (extraThickness > 0) + bounds.Expand(extraThickness); + + return bounds.Contains(point); + } + + virtual bool TestHit(const ImRect& rect, bool allowIntersect = true) const + { + if (!m_IsLive) + return false; + + const auto bounds = GetBounds(); + + return !ImRect_IsEmpty(bounds) && (allowIntersect ? bounds.Overlaps(rect) : rect.Contains(bounds)); + } + + virtual ImRect GetBounds() const = 0; + + virtual Node* AsNode() { return nullptr; } + virtual Pin* AsPin() { return nullptr; } + virtual Link* AsLink() { return nullptr; } +}; + +struct Pin final: Object +{ + using IdType = PinId; + + PinId m_ID; + PinKind m_Kind; + Node* m_Node; + ImRect m_Bounds; + ImRect m_Pivot; + Pin* m_PreviousPin; + ImU32 m_Color; + ImU32 m_BorderColor; + float m_BorderWidth; + float m_Rounding; + int m_Corners; + ImVec2 m_Dir; + float m_Strength; + float m_Radius; + float m_ArrowSize; + float m_ArrowWidth; + bool m_SnapLinkToDir; + bool m_HasConnection; + bool m_HadConnection; + + Pin(EditorContext* editor, PinId id, PinKind kind) + : Object(editor) + , m_ID(id) + , m_Kind(kind) + , m_Node(nullptr) + , m_Bounds() + , m_PreviousPin(nullptr) + , m_Color(IM_COL32_WHITE) + , m_BorderColor(IM_COL32_BLACK) + , m_BorderWidth(0) + , m_Rounding(0) + , m_Corners(0) + , m_Dir(0, 0) + , m_Strength(0) + , m_Radius(0) + , m_ArrowSize(0) + , m_ArrowWidth(0) + , m_SnapLinkToDir(true) + , m_HasConnection(false) + , m_HadConnection(false) + { + } + + virtual ObjectId ID() override { return m_ID; } + + virtual void Reset() override final + { + m_HadConnection = m_HasConnection && m_IsLive; + m_HasConnection = false; + + Object::Reset(); + } + + virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) override final; + + ImVec2 GetClosestPoint(const ImVec2& p) const; + ImLine GetClosestLine(const Pin* pin) const; + + virtual ImRect GetBounds() const override final { return m_Bounds; } + + virtual Pin* AsPin() override final { return this; } +}; + +enum class NodeType +{ + Node, + Group +}; + +enum class NodeRegion : uint8_t +{ + None = 0x00, + Top = 0x01, + Bottom = 0x02, + Left = 0x04, + Right = 0x08, + Center = 0x10, + Header = 0x20, + TopLeft = Top | Left, + TopRight = Top | Right, + BottomLeft = Bottom | Left, + BottomRight = Bottom | Right, +}; + +inline NodeRegion operator |(NodeRegion lhs, NodeRegion rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } +inline NodeRegion operator &(NodeRegion lhs, NodeRegion rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } + + +struct Node final: Object +{ + using IdType = NodeId; + + NodeId m_ID; + NodeType m_Type; + ImRect m_Bounds; + float m_ZPosition; + int m_Channel; + Pin* m_LastPin; + ImVec2 m_DragStart; + + ImU32 m_Color; + ImU32 m_BorderColor; + float m_BorderWidth; + float m_Rounding; + + ImU32 m_GroupColor; + ImU32 m_GroupBorderColor; + float m_GroupBorderWidth; + float m_GroupRounding; + ImRect m_GroupBounds; + + bool m_HighlightConnectedLinks; + + bool m_RestoreState; + bool m_CenterOnScreen; + + Node(EditorContext* editor, NodeId id) + : Object(editor) + , m_ID(id) + , m_Type(NodeType::Node) + , m_Bounds() + , m_ZPosition(0.0f) + , m_Channel(0) + , m_LastPin(nullptr) + , m_DragStart() + , m_Color(IM_COL32_WHITE) + , m_BorderColor(IM_COL32_BLACK) + , m_BorderWidth(0) + , m_Rounding(0) + , m_GroupBounds() + , m_HighlightConnectedLinks(false) + , m_RestoreState(false) + , m_CenterOnScreen(false) + { + } + + virtual ObjectId ID() override { return m_ID; } + + bool AcceptDrag() override; + void UpdateDrag(const ImVec2& offset) override; + bool EndDrag() override; // return true, when changed + ImVec2 DragStartLocation() override { return m_DragStart; } + + virtual bool IsSelectable() override { return true; } + + virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) override final; + void DrawBorder(ImDrawList* drawList, ImU32 color, float thickness = 1.0f, float offset = 0.0f); + + void GetGroupedNodes(std::vector& result, bool append = false); + + void CenterOnScreenInNextFrame() { m_CenterOnScreen = true; } + + ImRect GetRegionBounds(NodeRegion region) const; + NodeRegion GetRegion(const ImVec2& point) const; + + virtual ImRect GetBounds() const override final { return m_Bounds; } + + virtual Node* AsNode() override final { return this; } +}; + +struct Link final: Object +{ + using IdType = LinkId; + + LinkId m_ID; + Pin* m_StartPin; + Pin* m_EndPin; + ImU32 m_Color; + ImU32 m_HighlightColor; + float m_Thickness; + ImVec2 m_Start; + ImVec2 m_End; + + Link(EditorContext* editor, LinkId id) + : Object(editor) + , m_ID(id) + , m_StartPin(nullptr) + , m_EndPin(nullptr) + , m_Color(IM_COL32_WHITE) + , m_Thickness(1.0f) + { + } + + virtual ObjectId ID() override { return m_ID; } + + virtual bool IsSelectable() override { return true; } + + virtual void Draw(ImDrawList* drawList, DrawFlags flags = None) override final; + void Draw(ImDrawList* drawList, ImU32 color, float extraThickness = 0.0f) const; + + void UpdateEndpoints(); + + ImCubicBezierPoints GetCurve() const; + + virtual bool TestHit(const ImVec2& point, float extraThickness = 0.0f) const override final; + virtual bool TestHit(const ImRect& rect, bool allowIntersect = true) const override final; + + virtual ImRect GetBounds() const override final; + + virtual Link* AsLink() override final { return this; } +}; + +struct NodeSettings +{ + NodeId m_ID; + ImVec2 m_Location; + ImVec2 m_Size; + ImVec2 m_GroupSize; + bool m_WasUsed; + + bool m_Saved; + bool m_IsDirty; + SaveReasonFlags m_DirtyReason; + + NodeSettings(NodeId id) + : m_ID(id) + , m_Location(0, 0) + , m_Size(0, 0) + , m_GroupSize(0, 0) + , m_WasUsed(false) + , m_Saved(false) + , m_IsDirty(false) + , m_DirtyReason(SaveReasonFlags::None) + { + } + + void ClearDirty(); + void MakeDirty(SaveReasonFlags reason); + + json::value Serialize(); + + static bool Parse(const std::string& string, NodeSettings& settings); + static bool Parse(const json::value& data, NodeSettings& result); +}; + +struct Settings +{ + bool m_IsDirty; + SaveReasonFlags m_DirtyReason; + + vector m_Nodes; + vector m_Selection; + ImVec2 m_ViewScroll; + float m_ViewZoom; + ImRect m_VisibleRect; + + Settings() + : m_IsDirty(false) + , m_DirtyReason(SaveReasonFlags::None) + , m_ViewScroll(0, 0) + , m_ViewZoom(1.0f) + , m_VisibleRect() + { + } + + NodeSettings* AddNode(NodeId id); + NodeSettings* FindNode(NodeId id); + void RemoveNode(NodeId id); + + void ClearDirty(Node* node = nullptr); + void MakeDirty(SaveReasonFlags reason, Node* node = nullptr); + + std::string Serialize(); + + static bool Parse(const std::string& string, Settings& settings); +}; + +struct Control +{ + Object* HotObject; + Object* ActiveObject; + Object* ClickedObject; + Object* DoubleClickedObject; + Node* HotNode; + Node* ActiveNode; + Node* ClickedNode; + Node* DoubleClickedNode; + Pin* HotPin; + Pin* ActivePin; + Pin* ClickedPin; + Pin* DoubleClickedPin; + Link* HotLink; + Link* ActiveLink; + Link* ClickedLink; + Link* DoubleClickedLink; + bool BackgroundHot; + bool BackgroundActive; + int BackgroundClickButtonIndex; + int BackgroundDoubleClickButtonIndex; + + Control() + : Control(nullptr, nullptr, nullptr, nullptr, false, false, -1, -1) + { + } + + Control(Object* hotObject, Object* activeObject, Object* clickedObject, Object* doubleClickedObject, + bool backgroundHot, bool backgroundActive, int backgroundClickButtonIndex, int backgroundDoubleClickButtonIndex) + : HotObject(hotObject) + , ActiveObject(activeObject) + , ClickedObject(clickedObject) + , DoubleClickedObject(doubleClickedObject) + , HotNode(nullptr) + , ActiveNode(nullptr) + , ClickedNode(nullptr) + , DoubleClickedNode(nullptr) + , HotPin(nullptr) + , ActivePin(nullptr) + , ClickedPin(nullptr) + , DoubleClickedPin(nullptr) + , HotLink(nullptr) + , ActiveLink(nullptr) + , ClickedLink(nullptr) + , DoubleClickedLink(nullptr) + , BackgroundHot(backgroundHot) + , BackgroundActive(backgroundActive) + , BackgroundClickButtonIndex(backgroundClickButtonIndex) + , BackgroundDoubleClickButtonIndex(backgroundDoubleClickButtonIndex) + { + if (hotObject) + { + HotNode = hotObject->AsNode(); + HotPin = hotObject->AsPin(); + HotLink = hotObject->AsLink(); + + if (HotPin) + HotNode = HotPin->m_Node; + } + + if (activeObject) + { + ActiveNode = activeObject->AsNode(); + ActivePin = activeObject->AsPin(); + ActiveLink = activeObject->AsLink(); + } + + if (clickedObject) + { + ClickedNode = clickedObject->AsNode(); + ClickedPin = clickedObject->AsPin(); + ClickedLink = clickedObject->AsLink(); + } + + if (doubleClickedObject) + { + DoubleClickedNode = doubleClickedObject->AsNode(); + DoubleClickedPin = doubleClickedObject->AsPin(); + DoubleClickedLink = doubleClickedObject->AsLink(); + } + } +}; + +struct NavigateAction; +struct SizeAction; +struct DragAction; +struct SelectAction; +struct CreateItemAction; +struct DeleteItemsAction; +struct ContextMenuAction; +struct ShortcutAction; + +struct AnimationController; +struct FlowAnimationController; + +struct Animation +{ + enum State + { + Playing, + Stopped + }; + + EditorContext* Editor; + State m_State; + float m_Time; + float m_Duration; + + Animation(EditorContext* editor); + virtual ~Animation(); + + void Play(float duration); + void Stop(); + void Finish(); + void Update(); + + bool IsPlaying() const { return m_State == Playing; } + + float GetProgress() const { return m_Time / m_Duration; } + +protected: + virtual void OnPlay() {} + virtual void OnFinish() {} + virtual void OnStop() {} + + virtual void OnUpdate(float progress) { IM_UNUSED(progress); } +}; + +struct NavigateAnimation final: Animation +{ + NavigateAction& Action; + ImRect m_Start; + ImRect m_Target; + + NavigateAnimation(EditorContext* editor, NavigateAction& scrollAction); + + void NavigateTo(const ImRect& target, float duration); + +private: + void OnUpdate(float progress) override final; + void OnStop() override final; + void OnFinish() override final; +}; + +struct FlowAnimation final: Animation +{ + FlowAnimationController* Controller; + Link* m_Link; + float m_Speed; + float m_MarkerDistance; + float m_Offset; + + FlowAnimation(FlowAnimationController* controller); + + void Flow(Link* link, float markerDistance, float speed, float duration); + + void Draw(ImDrawList* drawList); + +private: + struct CurvePoint + { + float Distance; + ImVec2 Point; + }; + + ImVec2 m_LastStart; + ImVec2 m_LastEnd; + float m_PathLength; + vector m_Path; + + bool IsLinkValid() const; + bool IsPathValid() const; + void UpdatePath(); + void ClearPath(); + + ImVec2 SamplePath(float distance) const; + + void OnUpdate(float progress) override final; + void OnStop() override final; +}; + +struct AnimationController +{ + EditorContext* Editor; + + AnimationController(EditorContext* editor) + : Editor(editor) + { + } + + virtual ~AnimationController() + { + } + + virtual void Draw(ImDrawList* drawList) + { + IM_UNUSED(drawList); + } +}; + +struct FlowAnimationController final : AnimationController +{ + FlowAnimationController(EditorContext* editor); + virtual ~FlowAnimationController(); + + void Flow(Link* link, FlowDirection direction = FlowDirection::Forward); + + virtual void Draw(ImDrawList* drawList) override final; + + void Release(FlowAnimation* animation); + +private: + FlowAnimation* GetOrCreate(Link* link); + + vector m_Animations; + vector m_FreePool; +}; + +struct EditorAction +{ + enum AcceptResult { False, True, Possible }; + + EditorAction(EditorContext* editor) + : Editor(editor) + { + } + + virtual ~EditorAction() {} + + virtual const char* GetName() const = 0; + + virtual AcceptResult Accept(const Control& control) = 0; + virtual bool Process(const Control& control) = 0; + virtual void Reject() {} // celled when Accept return 'Possible' and was rejected + + virtual ImGuiMouseCursor GetCursor() { return ImGuiMouseCursor_Arrow; } + + virtual bool IsDragging() { return false; } + + virtual void ShowMetrics() {} + + virtual NavigateAction* AsNavigate() { return nullptr; } + virtual SizeAction* AsSize() { return nullptr; } + virtual DragAction* AsDrag() { return nullptr; } + virtual SelectAction* AsSelect() { return nullptr; } + virtual CreateItemAction* AsCreateItem() { return nullptr; } + virtual DeleteItemsAction* AsDeleteItems() { return nullptr; } + virtual ContextMenuAction* AsContextMenu() { return nullptr; } + virtual ShortcutAction* AsCutCopyPaste() { return nullptr; } + + EditorContext* Editor; +}; + +struct NavigateAction final: EditorAction +{ + enum class ZoomMode + { + None, + Exact, + WithMargin + }; + + enum class NavigationReason + { + Unknown, + MouseZoom, + Selection, + Object, + Content, + Edge + }; + + bool m_IsActive; + float m_Zoom; + ImRect m_VisibleRect; + ImVec2 m_Scroll; + ImVec2 m_ScrollStart; + ImVec2 m_ScrollDelta; + + NavigateAction(EditorContext* editor, ImGuiEx::Canvas& canvas); + + virtual const char* GetName() const override final { return "Navigate"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + + virtual void ShowMetrics() override final; + + virtual NavigateAction* AsNavigate() override final { return this; } + + void NavigateTo(const ImRect& bounds, ZoomMode zoomMode, float duration = -1.0f, NavigationReason reason = NavigationReason::Unknown); + void StopNavigation(); + void FinishNavigation(); + + bool MoveOverEdge(const ImVec2& canvasSize); + void StopMoveOverEdge(); + bool IsMovingOverEdge() const { return m_MovingOverEdge; } + ImVec2 GetMoveScreenOffset() const { return m_MoveScreenOffset; } + + void SetWindow(ImVec2 position, ImVec2 size); + ImVec2 GetWindowScreenPos() const { return m_WindowScreenPos; }; + ImVec2 GetWindowScreenSize() const { return m_WindowScreenSize; }; + + ImGuiEx::CanvasView GetView() const; + ImVec2 GetViewOrigin() const; + float GetViewScale() const; + + void SetViewRect(const ImRect& rect); + ImRect GetViewRect() const; + +private: + ImGuiEx::Canvas& m_Canvas; + ImVec2 m_WindowScreenPos; + ImVec2 m_WindowScreenSize; + + NavigateAnimation m_Animation; + NavigationReason m_Reason; + uint64_t m_LastSelectionId; + Object* m_LastObject; + bool m_MovingOverEdge; + ImVec2 m_MoveScreenOffset; + + const float* m_ZoomLevels; + int m_ZoomLevelCount; + + bool HandleZoom(const Control& control); + + void NavigateTo(const ImRect& target, float duration = -1.0f, NavigationReason reason = NavigationReason::Unknown); + + float GetNextZoom(float steps); + float MatchSmoothZoom(float steps); + float MatchZoom(int steps, float fallbackZoom); + int MatchZoomIndex(int direction); + + static const float s_DefaultZoomLevels[]; + static const int s_DefaultZoomLevelCount; +}; + +struct SizeAction final: EditorAction +{ + bool m_IsActive; + bool m_Clean; + Node* m_SizedNode; + + SizeAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Size"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + + virtual ImGuiMouseCursor GetCursor() override final { return m_Cursor; } + + virtual void ShowMetrics() override final; + + virtual SizeAction* AsSize() override final { return this; } + + virtual bool IsDragging() override final { return m_IsActive; } + + const ImRect& GetStartGroupBounds() const { return m_StartGroupBounds; } + +private: + NodeRegion GetRegion(Node* node); + ImGuiMouseCursor ChooseCursor(NodeRegion region); + + ImRect m_StartBounds; + ImRect m_StartGroupBounds; + ImVec2 m_LastSize; + ImVec2 m_MinimumSize; + ImVec2 m_LastDragOffset; + ed::NodeRegion m_Pivot; + ImGuiMouseCursor m_Cursor; +}; + +struct DragAction final: EditorAction +{ + bool m_IsActive; + bool m_Clear; + Object* m_DraggedObject; + vector m_Objects; + + DragAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Drag"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + + virtual ImGuiMouseCursor GetCursor() override final { return ImGuiMouseCursor_ResizeAll; } + + virtual bool IsDragging() override final { return m_IsActive; } + + virtual void ShowMetrics() override final; + + virtual DragAction* AsDrag() override final { return this; } +}; + +struct SelectAction final: EditorAction +{ + bool m_IsActive; + + bool m_SelectGroups; + bool m_SelectLinkMode; + bool m_CommitSelection; + ImVec2 m_StartPoint; + ImVec2 m_EndPoint; + vector m_CandidateObjects; + vector m_SelectedObjectsAtStart; + + Animation m_Animation; + + SelectAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Select"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + + virtual void ShowMetrics() override final; + + virtual bool IsDragging() override final { return m_IsActive; } + + virtual SelectAction* AsSelect() override final { return this; } + + void Draw(ImDrawList* drawList); +}; + +struct ContextMenuAction final: EditorAction +{ + enum Menu { None, Node, Pin, Link, Background }; + + Menu m_CandidateMenu; + Menu m_CurrentMenu; + ObjectId m_ContextId; + + ContextMenuAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Context Menu"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + virtual void Reject() override final; + + virtual void ShowMetrics() override final; + + virtual ContextMenuAction* AsContextMenu() override final { return this; } + + bool ShowNodeContextMenu(NodeId* nodeId); + bool ShowPinContextMenu(PinId* pinId); + bool ShowLinkContextMenu(LinkId* linkId); + bool ShowBackgroundContextMenu(); +}; + +struct ShortcutAction final: EditorAction +{ + enum Action { None, Cut, Copy, Paste, Duplicate, CreateNode }; + + bool m_IsActive; + bool m_InAction; + Action m_CurrentAction; + vector m_Context; + + ShortcutAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Shortcut"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + virtual void Reject() override final; + + virtual void ShowMetrics() override final; + + virtual ShortcutAction* AsCutCopyPaste() override final { return this; } + + bool Begin(); + void End(); + + bool AcceptCut(); + bool AcceptCopy(); + bool AcceptPaste(); + bool AcceptDuplicate(); + bool AcceptCreateNode(); +}; + +struct CreateItemAction final : EditorAction +{ + enum Stage + { + None, + Possible, + Create + }; + + enum Action + { + Unknown, + UserReject, + UserAccept + }; + + enum Type + { + NoItem, + Node, + Link + }; + + enum Result + { + True, + False, + Indeterminate + }; + + bool m_InActive; + Stage m_NextStage; + + Stage m_CurrentStage; + Type m_ItemType; + Action m_UserAction; + ImU32 m_LinkColor; + float m_LinkThickness; + Pin* m_LinkStart; + Pin* m_LinkEnd; + + bool m_IsActive; + Pin* m_DraggedPin; + + int m_LastChannel = -1; + + + CreateItemAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Create Item"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + + virtual ImGuiMouseCursor GetCursor() override final { return ImGuiMouseCursor_Arrow; } + + virtual void ShowMetrics() override final; + + virtual bool IsDragging() override final { return m_IsActive; } + + virtual CreateItemAction* AsCreateItem() override final { return this; } + + void SetStyle(ImU32 color, float thickness); + + bool Begin(); + void End(); + + Result RejectItem(); + Result AcceptItem(); + + Result QueryLink(PinId* startId, PinId* endId); + Result QueryNode(PinId* pinId); + +private: + bool m_IsInGlobalSpace; + + void DragStart(Pin* startPin); + void DragEnd(); + void DropPin(Pin* endPin); + void DropNode(); + void DropNothing(); +}; + +struct DeleteItemsAction final: EditorAction +{ + bool m_IsActive; + bool m_InInteraction; + + DeleteItemsAction(EditorContext* editor); + + virtual const char* GetName() const override final { return "Delete Items"; } + + virtual AcceptResult Accept(const Control& control) override final; + virtual bool Process(const Control& control) override final; + + virtual void ShowMetrics() override final; + + virtual DeleteItemsAction* AsDeleteItems() override final { return this; } + + bool Add(Object* object); + + bool Begin(); + void End(); + + bool QueryLink(LinkId* linkId, PinId* startId = nullptr, PinId* endId = nullptr); + bool QueryNode(NodeId* nodeId); + + bool AcceptItem(bool deleteDependencies); + void RejectItem(); + +private: + enum IteratorType { Unknown, Link, Node }; + enum UserAction { Undetermined, Accepted, Rejected }; + + void DeleteDeadLinks(NodeId nodeId); + void DeleteDeadPins(NodeId nodeId); + + bool QueryItem(ObjectId* itemId, IteratorType itemType); + void RemoveItem(bool deleteDependencies); + Object* DropCurrentItem(); + + vector m_ManuallyDeletedObjects; + + IteratorType m_CurrentItemType; + UserAction m_UserAction; + vector m_CandidateObjects; + int m_CandidateItemIndex; +}; + +struct NodeBuilder +{ + EditorContext* const Editor; + + Node* m_CurrentNode; + Pin* m_CurrentPin; + + ImRect m_NodeRect; + + ImRect m_PivotRect; + ImVec2 m_PivotAlignment; + ImVec2 m_PivotSize; + ImVec2 m_PivotScale; + bool m_ResolvePinRect; + bool m_ResolvePivot; + + ImRect m_GroupBounds; + bool m_IsGroup; + + ImDrawListSplitter m_Splitter; + ImDrawListSplitter m_PinSplitter; + + NodeBuilder(EditorContext* editor); + ~NodeBuilder(); + + void Begin(NodeId nodeId); + void End(); + + void BeginPin(PinId pinId, PinKind kind); + void EndPin(); + + void PinRect(const ImVec2& a, const ImVec2& b); + void PinPivotRect(const ImVec2& a, const ImVec2& b); + void PinPivotSize(const ImVec2& size); + void PinPivotScale(const ImVec2& scale); + void PinPivotAlignment(const ImVec2& alignment); + + void Group(const ImVec2& size); + + ImDrawList* GetUserBackgroundDrawList() const; + ImDrawList* GetUserBackgroundDrawList(Node* node) const; +}; + +struct HintBuilder +{ + EditorContext* const Editor; + bool m_IsActive; + Node* m_CurrentNode; + float m_LastFringe = 1.0f; + int m_LastChannel = 0; + + HintBuilder(EditorContext* editor); + + bool Begin(NodeId nodeId); + void End(); + + ImVec2 GetGroupMin(); + ImVec2 GetGroupMax(); + + ImDrawList* GetForegroundDrawList(); + ImDrawList* GetBackgroundDrawList(); +}; + +struct Style: ax::NodeEditor::Style +{ + void PushColor(StyleColor colorIndex, const ImVec4& color); + void PopColor(int count = 1); + + void PushVar(StyleVar varIndex, float value); + void PushVar(StyleVar varIndex, const ImVec2& value); + void PushVar(StyleVar varIndex, const ImVec4& value); + void PopVar(int count = 1); + + const char* GetColorName(StyleColor colorIndex) const; + +private: + struct ColorModifier + { + StyleColor Index; + ImVec4 Value; + }; + + struct VarModifier + { + StyleVar Index; + ImVec4 Value; + }; + + float* GetVarFloatAddr(StyleVar idx); + ImVec2* GetVarVec2Addr(StyleVar idx); + ImVec4* GetVarVec4Addr(StyleVar idx); + + vector m_ColorStack; + vector m_VarStack; +}; + +struct Config: ax::NodeEditor::Config +{ + Config(const ax::NodeEditor::Config* config); + + std::string Load(); + std::string LoadNode(NodeId nodeId); + + void BeginSave(); + bool Save(const std::string& data, SaveReasonFlags flags); + bool SaveNode(NodeId nodeId, const std::string& data, SaveReasonFlags flags); + void EndSave(); +}; + +enum class SuspendFlags : uint8_t +{ + None = 0, + KeepSplitter = 1 +}; + +inline SuspendFlags operator |(SuspendFlags lhs, SuspendFlags rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } +inline SuspendFlags operator &(SuspendFlags lhs, SuspendFlags rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } + + +struct EditorContext +{ + EditorContext(const ax::NodeEditor::Config* config = nullptr); + ~EditorContext(); + + const Config& GetConfig() const { return m_Config; } + + Style& GetStyle() { return m_Style; } + + void Begin(const char* id, const ImVec2& size = ImVec2(0, 0)); + void End(); + + bool DoLink(LinkId id, PinId startPinId, PinId endPinId, ImU32 color, float thickness); + + + NodeBuilder& GetNodeBuilder() { return m_NodeBuilder; } + HintBuilder& GetHintBuilder() { return m_HintBuilder; } + + EditorAction* GetCurrentAction() { return m_CurrentAction; } + + CreateItemAction& GetItemCreator() { return m_CreateItemAction; } + DeleteItemsAction& GetItemDeleter() { return m_DeleteItemsAction; } + ContextMenuAction& GetContextMenu() { return m_ContextMenuAction; } + ShortcutAction& GetShortcut() { return m_ShortcutAction; } + + const ImGuiEx::CanvasView& GetView() const { return m_Canvas.View(); } + const ImRect& GetViewRect() const { return m_Canvas.ViewRect(); } + const ImRect& GetRect() const { return m_Canvas.Rect(); } + + void SetNodePosition(NodeId nodeId, const ImVec2& screenPosition); + void SetGroupSize(NodeId nodeId, const ImVec2& size); + ImVec2 GetNodePosition(NodeId nodeId); + ImVec2 GetNodeSize(NodeId nodeId); + + void SetNodeZPosition(NodeId nodeId, float z); + float GetNodeZPosition(NodeId nodeId); + + void MarkNodeToRestoreState(Node* node); + void UpdateNodeState(Node* node); + + void RemoveSettings(Object* object); + + void ClearSelection(); + void SelectObject(Object* object); + void DeselectObject(Object* object); + void SetSelectedObject(Object* object); + void ToggleObjectSelection(Object* object); + bool IsSelected(Object* object); + const vector& GetSelectedObjects(); + bool IsAnyNodeSelected(); + bool IsAnyLinkSelected(); + bool HasSelectionChanged(); + uint64_t GetSelectionId() const { return m_SelectionId; } + + Node* FindNodeAt(const ImVec2& p); + void FindNodesInRect(const ImRect& r, vector& result, bool append = false, bool includeIntersecting = true); + void FindLinksInRect(const ImRect& r, vector& result, bool append = false); + + bool HasAnyLinks(NodeId nodeId) const; + bool HasAnyLinks(PinId pinId) const; + + int BreakLinks(NodeId nodeId); + int BreakLinks(PinId pinId); + + void FindLinksForNode(NodeId nodeId, vector& result, bool add = false); + + bool PinHadAnyLinks(PinId pinId); + + ImVec2 ToCanvas(const ImVec2& point) const { return m_Canvas.ToLocal(point); } + ImVec2 ToScreen(const ImVec2& point) const { return m_Canvas.FromLocal(point); } + + void NotifyLinkDeleted(Link* link); + + void Suspend(SuspendFlags flags = SuspendFlags::None); + void Resume(SuspendFlags flags = SuspendFlags::None); + bool IsSuspended(); + + bool IsFocused(); + bool IsHovered() const; + bool IsHoveredWithoutOverlapp() const; + bool CanAcceptUserInput() const; + + void MakeDirty(SaveReasonFlags reason); + void MakeDirty(SaveReasonFlags reason, Node* node); + + int CountLiveNodes() const; + int CountLivePins() const; + int CountLiveLinks() const; + + Pin* CreatePin(PinId id, PinKind kind); + Node* CreateNode(NodeId id); + Link* CreateLink(LinkId id); + + Node* FindNode(NodeId id); + Pin* FindPin(PinId id); + Link* FindLink(LinkId id); + Object* FindObject(ObjectId id); + + Node* GetNode(NodeId id); + Pin* GetPin(PinId id, PinKind kind); + Link* GetLink(LinkId id); + + Link* FindLinkAt(const ImVec2& p); + + template + ImRect GetBounds(const std::vector& objects) + { + ImRect bounds(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + + for (auto object : objects) + if (object->m_IsLive) + bounds.Add(object->GetBounds()); + + if (ImRect_IsEmpty(bounds)) + bounds = ImRect(); + + return bounds; + } + + template + ImRect GetBounds(const std::vector>& objects) + { + ImRect bounds(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + + for (auto object : objects) + if (object.m_Object->m_IsLive) + bounds.Add(object.m_Object->GetBounds()); + + if (ImRect_IsEmpty(bounds)) + bounds = ImRect(); + + return bounds; + } + + ImRect GetSelectionBounds() { return GetBounds(m_SelectedObjects); } + ImRect GetContentBounds() { return GetBounds(m_Nodes); } + + ImU32 GetColor(StyleColor colorIndex) const; + ImU32 GetColor(StyleColor colorIndex, float alpha) const; + + int GetNodeIds(NodeId* nodes, int size) const; + + void NavigateTo(const ImRect& bounds, bool zoomIn = false, float duration = -1) + { + auto zoomMode = zoomIn ? NavigateAction::ZoomMode::WithMargin : NavigateAction::ZoomMode::None; + m_NavigateAction.NavigateTo(bounds, zoomMode, duration); + } + + void RegisterAnimation(Animation* animation); + void UnregisterAnimation(Animation* animation); + + void Flow(Link* link, FlowDirection direction); + + void SetUserContext(bool globalSpace = false); + + void EnableShortcuts(bool enable); + bool AreShortcutsEnabled(); + + NodeId GetHoveredNode() const { return m_HoveredNode; } + PinId GetHoveredPin() const { return m_HoveredPin; } + LinkId GetHoveredLink() const { return m_HoveredLink; } + NodeId GetDoubleClickedNode() const { return m_DoubleClickedNode; } + PinId GetDoubleClickedPin() const { return m_DoubleClickedPin; } + LinkId GetDoubleClickedLink() const { return m_DoubleClickedLink; } + bool IsBackgroundClicked() const { return m_BackgroundClickButtonIndex >= 0; } + bool IsBackgroundDoubleClicked() const { return m_BackgroundDoubleClickButtonIndex >= 0; } + ImGuiMouseButton GetBackgroundClickButtonIndex() const { return m_BackgroundClickButtonIndex; } + ImGuiMouseButton GetBackgroundDoubleClickButtonIndex() const { return m_BackgroundDoubleClickButtonIndex; } + + float AlignPointToGrid(float p) const + { + if (!ImGui::GetIO().KeyAlt) + return p - ImFmod(p, 16.0f); + else + return p; + } + + ImVec2 AlignPointToGrid(const ImVec2& p) const + { + return ImVec2(AlignPointToGrid(p.x), AlignPointToGrid(p.y)); + } + + ImDrawList* GetDrawList() { return m_DrawList; } + +private: + void LoadSettings(); + void SaveSettings(); + + Control BuildControl(bool allowOffscreen); + + void ShowMetrics(const Control& control); + + void UpdateAnimations(); + + Config m_Config; + + ImGuiID m_EditorActiveId; + bool m_IsFirstFrame; + bool m_IsFocused; + bool m_IsHovered; + bool m_IsHoveredWithoutOverlapp; + + bool m_ShortcutsEnabled; + + Style m_Style; + + vector> m_Nodes; + vector> m_Pins; + vector> m_Links; + + vector m_SelectedObjects; + + vector m_LastSelectedObjects; + uint64_t m_SelectionId; + + Link* m_LastActiveLink; + + vector m_LiveAnimations; + vector m_LastLiveAnimations; + + ImGuiEx::Canvas m_Canvas; + bool m_IsCanvasVisible; + + NodeBuilder m_NodeBuilder; + HintBuilder m_HintBuilder; + + EditorAction* m_CurrentAction; + NavigateAction m_NavigateAction; + SizeAction m_SizeAction; + DragAction m_DragAction; + SelectAction m_SelectAction; + ContextMenuAction m_ContextMenuAction; + ShortcutAction m_ShortcutAction; + CreateItemAction m_CreateItemAction; + DeleteItemsAction m_DeleteItemsAction; + + vector m_AnimationControllers; + FlowAnimationController m_FlowAnimationController; + + NodeId m_HoveredNode; + PinId m_HoveredPin; + LinkId m_HoveredLink; + NodeId m_DoubleClickedNode; + PinId m_DoubleClickedPin; + LinkId m_DoubleClickedLink; + int m_BackgroundClickButtonIndex; + int m_BackgroundDoubleClickButtonIndex; + + bool m_IsInitialized; + Settings m_Settings; + + ImDrawList* m_DrawList; + int m_ExternalChannel; + ImDrawListSplitter m_Splitter; +}; + + +//------------------------------------------------------------------------------ +} // namespace Detail +} // namespace Editor +} // namespace ax + + +//------------------------------------------------------------------------------ +# include "imgui_node_editor_internal.inl" + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_NODE_EDITOR_INTERNAL_H__ diff --git a/cpp/vendor/imgui-node-editor/imgui_node_editor_internal.inl b/cpp/vendor/imgui-node-editor/imgui_node_editor_internal.inl new file mode 100644 index 00000000..df0dd257 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/imgui_node_editor_internal.inl @@ -0,0 +1,65 @@ +//------------------------------------------------------------------------------ +// VERSION 0.9.1 +// +// LICENSE +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// CREDITS +// Written by Michal Cichon +//------------------------------------------------------------------------------ +# ifndef __IMGUI_NODE_EDITOR_INTERNAL_INL__ +# define __IMGUI_NODE_EDITOR_INTERNAL_INL__ +# pragma once + + +//------------------------------------------------------------------------------ +# include "imgui_node_editor_internal.h" + + +//------------------------------------------------------------------------------ +namespace ax { +namespace NodeEditor { +namespace Detail { + + +//------------------------------------------------------------------------------ +//inline ImRect ToRect(const ax::rectf& rect) +//{ +// return ImRect( +// to_imvec(rect.top_left()), +// to_imvec(rect.bottom_right()) +// ); +//} +// +//inline ImRect ToRect(const ax::rect& rect) +//{ +// return ImRect( +// to_imvec(rect.top_left()), +// to_imvec(rect.bottom_right()) +// ); +//} + +inline ImRect ImGui_GetItemRect() +{ + return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); +} + +inline ImVec2 ImGui_GetMouseClickPos(ImGuiMouseButton buttonIndex) +{ + if (ImGui::IsMouseDown(buttonIndex)) + return ImGui::GetIO().MouseClickedPos[buttonIndex]; + else + return ImGui::GetMousePos(); +} + + +//------------------------------------------------------------------------------ +} // namespace Detail +} // namespace Editor +} // namespace ax + + +//------------------------------------------------------------------------------ +# endif // __IMGUI_NODE_EDITOR_INTERNAL_INL__ diff --git a/cpp/vendor/imgui-node-editor/misc/cmake-modules/FindScopeGuard.cmake b/cpp/vendor/imgui-node-editor/misc/cmake-modules/FindScopeGuard.cmake new file mode 100644 index 00000000..48e4d7fe --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/cmake-modules/FindScopeGuard.cmake @@ -0,0 +1,17 @@ +if (TARGET ScopeGuard) + return() +endif() + +set(_ScopeGuard_SourceDir ${IMGUI_NODE_EDITOR_ROOT_DIR}/external/ScopeGuard) +set(_ScopeGuard_BinaryDir ${CMAKE_BINARY_DIR}/external/ScopeGuard) + +add_subdirectory(${_ScopeGuard_SourceDir} ${_ScopeGuard_BinaryDir}) + +include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) + +find_package_handle_standard_args( + ScopeGuard + REQUIRED_VARS + _ScopeGuard_SourceDir +) + diff --git a/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findgl3w.cmake b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findgl3w.cmake new file mode 100644 index 00000000..452cb750 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findgl3w.cmake @@ -0,0 +1,17 @@ +if (TARGET gl3w) + return() +endif() + +set(_gl3w_SourceDir ${IMGUI_NODE_EDITOR_ROOT_DIR}/external/gl3w) +set(_gl3w_BinaryDir ${CMAKE_BINARY_DIR}/external/gl3w) + +add_subdirectory(${_gl3w_SourceDir} ${_gl3w_BinaryDir}) + +include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) + +find_package_handle_standard_args( + gl3w + REQUIRED_VARS + _gl3w_SourceDir +) + diff --git a/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui.cmake b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui.cmake new file mode 100644 index 00000000..67a0ad93 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui.cmake @@ -0,0 +1,17 @@ +if (TARGET imgui) + return() +endif() + +set(_imgui_SourceDir ${IMGUI_NODE_EDITOR_ROOT_DIR}/external/imgui) +set(_imgui_BinaryDir ${CMAKE_BINARY_DIR}/external/imgui) + +add_subdirectory(${_imgui_SourceDir} ${_imgui_BinaryDir}) + +include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) + +find_package_handle_standard_args( + imgui + REQUIRED_VARS + _imgui_SourceDir +) + diff --git a/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui_node_editor.cmake b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui_node_editor.cmake new file mode 100644 index 00000000..a8c295f7 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findimgui_node_editor.cmake @@ -0,0 +1,49 @@ +if (TARGET imgui_node_editor) + return() +endif() + +#set(_imgui_node_editor_SourceDir ${IMGUI_NODE_EDITOR_ROOT_DIR}) +#set(_imgui_node_editor_BinaryDir ${CMAKE_BINARY_DIR}/NodeEditor) + +#add_subdirectory(${_imgui_node_editor_SourceDir} ${_imgui_node_editor_BinaryDir}) + +find_package(imgui REQUIRED) + +set(_imgui_node_editor_Sources + ${IMGUI_NODE_EDITOR_ROOT_DIR}/crude_json.cpp + ${IMGUI_NODE_EDITOR_ROOT_DIR}/crude_json.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_bezier_math.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_bezier_math.inl + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_canvas.cpp + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_canvas.cpp + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_canvas.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_canvas.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_extra_math.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_extra_math.inl + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_node_editor_api.cpp + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_node_editor_internal.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_node_editor_internal.inl + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_node_editor.cpp + ${IMGUI_NODE_EDITOR_ROOT_DIR}/imgui_node_editor.h + ${IMGUI_NODE_EDITOR_ROOT_DIR}/misc/imgui_node_editor.natvis +) + +add_library(imgui_node_editor STATIC + ${_imgui_node_editor_Sources} +) + +target_include_directories(imgui_node_editor PUBLIC + ${IMGUI_NODE_EDITOR_ROOT_DIR} +) + +target_link_libraries(imgui_node_editor PUBLIC imgui) + +source_group(TREE ${IMGUI_NODE_EDITOR_ROOT_DIR} FILES ${_imgui_node_editor_Sources}) + +include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) + +find_package_handle_standard_args( + imgui_node_editor + REQUIRED_VARS + IMGUI_NODE_EDITOR_ROOT_DIR +) \ No newline at end of file diff --git a/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findstb_image.cmake b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findstb_image.cmake new file mode 100644 index 00000000..34bffac8 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/cmake-modules/Findstb_image.cmake @@ -0,0 +1,17 @@ +if (TARGET stb_image) + return() +endif() + +set(_stb_image_SourceDir ${IMGUI_NODE_EDITOR_ROOT_DIR}/external/stb_image) +set(_stb_image_BinaryDir ${CMAKE_BINARY_DIR}/external/stb_image) + +add_subdirectory(${_stb_image_SourceDir} ${_stb_image_BinaryDir}) + +include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) + +find_package_handle_standard_args( + stb_image + REQUIRED_VARS + _stb_image_SourceDir +) + diff --git a/cpp/vendor/imgui-node-editor/misc/crude_json.natvis b/cpp/vendor/imgui-node-editor/misc/crude_json.natvis new file mode 100644 index 00000000..158f2087 --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/crude_json.natvis @@ -0,0 +1,18 @@ + + + + + {m_Type,en} {*(crude_json::object*)&m_Storage,view(simple)} + {m_Type,en} {*(crude_json::array*)&m_Storage,view(simple)} + {*(crude_json::string*)&m_Storage,view(simple)} + {*(crude_json::boolean*)&m_Storage} + {*(crude_json::number*)&m_Storage,g} + {m_Type,en} + *(crude_json::string*)&m_Storage + + *(crude_json::object*)&m_Storage,view(simple) + *(crude_json::array*)&m_Storage,view(simple) + + + + diff --git a/cpp/vendor/imgui-node-editor/misc/imgui_node_editor.natvis b/cpp/vendor/imgui-node-editor/misc/imgui_node_editor.natvis new file mode 100644 index 00000000..e95ac0dc --- /dev/null +++ b/cpp/vendor/imgui-node-editor/misc/imgui_node_editor.natvis @@ -0,0 +1,64 @@ + + + + + {m_Value} + + m_Value + ($T1*)m_Value + + + + + Node {m_Value} + + m_Value + + + + + Link {m_Value} + + m_Value + + + + + Pin {m_Value} + + m_Value + + + + + {second} + + second + + + + + + + + + + + null + {*object_ptr()} : object + {*array_ptr()} : array + {*string_ptr()} : string + {*boolean_ptr()} : boolean + {*number_ptr(),g} : number + discarded + + + *object_ptr(),view(simple) + *array_ptr(),view(simple) + *string_ptr(),view(simple) + *boolean_ptr() + *number_ptr() + + + +