From 26cc4165e7a0aad94f9db693521164851316bd8e Mon Sep 17 00:00:00 2001 From: Auburn Date: Tue, 18 Apr 2023 01:01:42 +0100 Subject: [PATCH 1/9] Fix compile with latest ImGui IMGUI_DEFINE_MATH_OPERATORS now needs to be defined before #include --- imnodes.cpp | 4 ---- imnodes_internal.h | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/imnodes.cpp b/imnodes.cpp index 7689cb1..e8bddb8 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -6,12 +6,8 @@ // [SECTION] render helpers // [SECTION] API implementation -#include "imnodes.h" #include "imnodes_internal.h" -#define IMGUI_DEFINE_MATH_OPERATORS -#include - // Check minimum ImGui version #define MINIMUM_COMPATIBLE_IMGUI_VERSION 17400 #if IMGUI_VERSION_NUM < MINIMUM_COMPATIBLE_IMGUI_VERSION diff --git a/imnodes_internal.h b/imnodes_internal.h index 593ab49..8cfb5d7 100644 --- a/imnodes_internal.h +++ b/imnodes_internal.h @@ -1,11 +1,11 @@ #pragma once -#include "imnodes.h" - -#include #define IMGUI_DEFINE_MATH_OPERATORS +#include #include +#include "imnodes.h" + #include // the structure of this file: From b7fe95a1b6b24a96471c29d487839a97c51f5880 Mon Sep 17 00:00:00 2001 From: Auburn Date: Wed, 19 Apr 2023 01:18:46 +0100 Subject: [PATCH 2/9] Fix CMake config to allow using imnodes as sub project, option for building examples, custom imgui linking --- CMakeLists.txt | 125 +++++++++++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a397b2f..dc6732d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,25 @@ project(imnodes) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) -find_package(imgui CONFIG REQUIRED) -find_package(SDL2 CONFIG REQUIRED) + +# determine whether this is a standalone project or included by other projects +if (NOT DEFINED IMNODES_STANDALONE_PROJECT) + if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(IMNODES_STANDALONE_PROJECT ON) + else() + set(IMNODES_STANDALONE_PROJECT OFF) + endif () +endif() + +# cmake options +option(IMNODES_EXAMPLES "Build examples" ${IMNODES_STANDALONE_PROJECT}) + +# allow custom imgui target name since this can vary because imgui doesn'' +if(NOT DEFINED IMNODES_IMGUI_TARGET_NAME) + find_package(imgui CONFIG REQUIRED) + set(IMNODES_IMGUI_TARGET_NAME imgui::imgui) +endif() + if(MSVC) add_compile_definitions(SDL_MAIN_HANDLED) @@ -22,59 +39,63 @@ target_sources(imnodes PRIVATE imnodes.h imnodes_internal.h imnodes.cpp) -target_include_directories(imnodes PUBLIC ${CMAKE_SOURCE_DIR}) -target_link_libraries(imnodes PUBLIC imgui::imgui) +target_include_directories(imnodes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(imnodes PUBLIC ${IMNODES_IMGUI_TARGET_NAME}) # Example projects +if(IMNODES_EXAMPLES) -add_executable(colornode - ${CMAKE_SOURCE_DIR}/imnodes.cpp - ${CMAKE_SOURCE_DIR}/example/main.cpp - ${CMAKE_SOURCE_DIR}/example/color_node_editor.cpp) -target_link_libraries(colornode imnodes SDL2::SDL2) -if (APPLE) - target_link_libraries(colornode "-framework OpenGL") -elseif(MSVC) - target_link_libraries(colornode "opengl32") -else() - target_link_libraries(colornode X11 Xext GL) -endif() + find_package(SDL2 CONFIG REQUIRED) -add_executable(multieditor - ${CMAKE_SOURCE_DIR}/imnodes.cpp - ${CMAKE_SOURCE_DIR}/example/main.cpp - ${CMAKE_SOURCE_DIR}/example/multi_editor.cpp) -target_link_libraries(multieditor imnodes SDL2::SDL2) -if (APPLE) - target_link_libraries(multieditor "-framework OpenGL") -elseif(MSVC) - target_link_libraries(multieditor "opengl32") -else() - target_link_libraries(multieditor X11 Xext GL) -endif() + add_executable(colornode + ${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/color_node_editor.cpp) + target_link_libraries(colornode imnodes SDL2::SDL2) + if (APPLE) + target_link_libraries(colornode "-framework OpenGL") + elseif(MSVC) + target_link_libraries(colornode "opengl32") + else() + target_link_libraries(colornode X11 Xext GL) + endif() -add_executable(saveload - ${CMAKE_SOURCE_DIR}/imnodes.cpp - ${CMAKE_SOURCE_DIR}/example/main.cpp - ${CMAKE_SOURCE_DIR}/example/save_load.cpp) -target_link_libraries(saveload imnodes SDL2::SDL2) -if (APPLE) - target_link_libraries(saveload "-framework OpenGL") -elseif(MSVC) - target_link_libraries(saveload "opengl32") -else() - target_link_libraries(saveload X11 Xext GL) -endif() + add_executable(multieditor + ${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/multi_editor.cpp) + target_link_libraries(multieditor imnodes SDL2::SDL2) + if (APPLE) + target_link_libraries(multieditor "-framework OpenGL") + elseif(MSVC) + target_link_libraries(multieditor "opengl32") + else() + target_link_libraries(multieditor X11 Xext GL) + endif() -add_executable(hello - ${CMAKE_SOURCE_DIR}/imnodes.cpp - ${CMAKE_SOURCE_DIR}/example/main.cpp - ${CMAKE_SOURCE_DIR}/example/hello.cpp) -target_link_libraries(hello imnodes SDL2::SDL2) -if (APPLE) - target_link_libraries(hello "-framework OpenGL") -elseif(MSVC) - target_link_libraries(hello "opengl32") -else() - target_link_libraries(hello X11 Xext GL) -endif() + add_executable(saveload + ${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/save_load.cpp) + target_link_libraries(saveload imnodes SDL2::SDL2) + if (APPLE) + target_link_libraries(saveload "-framework OpenGL") + elseif(MSVC) + target_link_libraries(saveload "opengl32") + else() + target_link_libraries(saveload X11 Xext GL) + endif() + + add_executable(hello + ${CMAKE_CURRENT_SOURCE_DIR}/imnodes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/example/hello.cpp) + target_link_libraries(hello imnodes SDL2::SDL2) + if (APPLE) + target_link_libraries(hello "-framework OpenGL") + elseif(MSVC) + target_link_libraries(hello "opengl32") + else() + target_link_libraries(hello X11 Xext GL) + endif() +endif() \ No newline at end of file From 1aa48f4af2a4f9f1b9a6ed53fe858ed76646b233 Mon Sep 17 00:00:00 2001 From: Auburn Date: Wed, 19 Apr 2023 01:23:04 +0100 Subject: [PATCH 3/9] Fix comment typo --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc6732d..f84b551 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ endif() # cmake options option(IMNODES_EXAMPLES "Build examples" ${IMNODES_STANDALONE_PROJECT}) -# allow custom imgui target name since this can vary because imgui doesn'' +# allow custom imgui target name since this can vary because imgui doesn't natively include a CMakeLists.txt if(NOT DEFINED IMNODES_IMGUI_TARGET_NAME) find_package(imgui CONFIG REQUIRED) set(IMNODES_IMGUI_TARGET_NAME imgui::imgui) From 0cda3b818e9bc4ede43b094796ec95792bf5fbb7 Mon Sep 17 00:00:00 2001 From: Auburn Date: Sat, 3 Feb 2024 23:45:48 +0000 Subject: [PATCH 4/9] Add zoom functionality Uses second ImGui context for fully functional scaling in node editor --- imnodes.cpp | 233 +++++++++++++++++++++++++++++++++++---------- imnodes.h | 4 + imnodes_internal.h | 15 +-- 3 files changed, 197 insertions(+), 55 deletions(-) diff --git a/imnodes.cpp b/imnodes.cpp index e8bddb8..55710a5 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -562,11 +562,7 @@ ImVec2 GetScreenSpacePinCoordinates(const ImNodesEditorContext& editor, const Im bool MouseInCanvas() { - // This flag should be true either when hovering or clicking something in the canvas. - const bool is_window_hovered_or_focused = ImGui::IsWindowHovered() || ImGui::IsWindowFocused(); - - return is_window_hovered_or_focused && - GImNodes->CanvasRectScreenSpace.Contains(ImGui::GetMousePos()); + return GImNodes->IsHovered; } void BeginNodeSelection(ImNodesEditorContext& editor, const int node_idx) @@ -1049,7 +1045,7 @@ void ClickInteractionUpdate(ImNodesEditorContext& editor) cubic_bezier.P2, cubic_bezier.P3, GImNodes->Style.Colors[ImNodesCol_Link], - GImNodes->Style.LinkThickness, + GImNodes->Style.LinkThickness / editor.ZoomScale, cubic_bezier.NumSegments); const bool link_creation_on_snap = @@ -1348,6 +1344,46 @@ void DrawGrid(ImNodesEditorContext& editor, const ImVec2& canvas_size) } } +inline void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) +{ + // TODO optimize if vtx_start == 0 || if idx_start == 0 + ImDrawList* dl = ImGui::GetWindowDrawList(); + const int vtx_start = dl->VtxBuffer.size(); + const int idx_start = dl->IdxBuffer.size(); + dl->VtxBuffer.resize(dl->VtxBuffer.size() + src->VtxBuffer.size()); + dl->IdxBuffer.resize(dl->IdxBuffer.size() + src->IdxBuffer.size()); + dl->CmdBuffer.reserve(dl->CmdBuffer.size() + src->CmdBuffer.size()); + dl->_VtxWritePtr = dl->VtxBuffer.Data + vtx_start; + dl->_IdxWritePtr = dl->IdxBuffer.Data + idx_start; + const ImDrawVert* vtx_read = src->VtxBuffer.Data; + const ImDrawIdx* idx_read = src->IdxBuffer.Data; + for (int i = 0, c = src->VtxBuffer.size(); i < c; ++i) + { + dl->_VtxWritePtr[i].uv = vtx_read[i].uv; + dl->_VtxWritePtr[i].col = vtx_read[i].col; + dl->_VtxWritePtr[i].pos = vtx_read[i].pos * scale + origin; + } + for (int i = 0, c = src->IdxBuffer.size(); i < c; ++i) + { + dl->_IdxWritePtr[i] = idx_read[i] + vtx_start; + } + for (int i = 0, c = src->CmdBuffer.size(); i < c; ++i) + { + ImDrawCmd cmd = src->CmdBuffer[i]; + cmd.IdxOffset += idx_start; + IM_ASSERT(cmd.VtxOffset == 0); + cmd.ClipRect.x = cmd.ClipRect.x * scale + origin.x; + cmd.ClipRect.y = cmd.ClipRect.y * scale + origin.y; + cmd.ClipRect.z = cmd.ClipRect.z * scale + origin.x; + cmd.ClipRect.w = cmd.ClipRect.w * scale + origin.y; + dl->CmdBuffer.push_back(cmd); + } + + dl->_VtxCurrentIdx += src->VtxBuffer.size(); + dl->_VtxWritePtr = dl->VtxBuffer.Data + dl->VtxBuffer.size(); + dl->_IdxWritePtr = dl->IdxBuffer.Data + dl->IdxBuffer.size(); +} + struct QuadOffsets { ImVec2 TopLeft, BottomLeft, BottomRight, TopRight; @@ -1623,7 +1659,7 @@ void DrawLink(ImNodesEditorContext& editor, const int link_idx) cubic_bezier.P2, cubic_bezier.P3, link_color, - GImNodes->Style.LinkThickness, + GImNodes->Style.LinkThickness / editor.ZoomScale, cubic_bezier.NumSegments); } @@ -1680,6 +1716,11 @@ void EndPinAttribute() void Initialize(ImNodesContext* context) { + context->ZoomImgCtx = ImGui::CreateContext(ImGui::GetIO().Fonts); + context->ZoomImgCtx->IO.IniFilename = nullptr; + context->OriginalImgCtx = nullptr; + + context->CanvasOriginalOrigin = ImVec2(0.0f, 0.0f); context->CanvasOriginScreenSpace = ImVec2(0.0f, 0.0f); context->CanvasRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f)); context->CurrentScope = ImNodesScope_None; @@ -1696,7 +1737,11 @@ void Initialize(ImNodesContext* context) StyleColorsDark(&context->Style); } -void Shutdown(ImNodesContext* ctx) { EditorContextFree(ctx->DefaultEditorCtx); } +void Shutdown(ImNodesContext* ctx) +{ + EditorContextFree(ctx->DefaultEditorCtx); + ImGui::DestroyContext(ctx->ZoomImgCtx); +} // [SECTION] minimap @@ -1717,8 +1762,8 @@ static inline bool IsMiniMapHovered() static inline void CalcMiniMapLayout() { ImNodesEditorContext& editor = EditorContextGet(); - const ImVec2 offset = GImNodes->Style.MiniMapOffset; - const ImVec2 border = GImNodes->Style.MiniMapPadding; + const ImVec2 offset = GImNodes->Style.MiniMapOffset / editor.ZoomScale; + const ImVec2 border = GImNodes->Style.MiniMapPadding / editor.ZoomScale; const ImRect editor_rect = GImNodes->CanvasRectScreenSpace; // Compute the size of the mini-map area @@ -1814,7 +1859,7 @@ static void MiniMapDrawNode(ImNodesEditorContext& editor, const int node_idx) node_rect.Min, node_rect.Max, mini_map_node_background, mini_map_node_rounding); GImNodes->CanvasDrawList->AddRect( - node_rect.Min, node_rect.Max, mini_map_node_outline, mini_map_node_rounding); + node_rect.Min, node_rect.Max, mini_map_node_outline, mini_map_node_rounding, 0, 1 / editor.ZoomScale); } static void MiniMapDrawLink(ImNodesEditorContext& editor, const int link_idx) @@ -1854,7 +1899,7 @@ static void MiniMapDrawLink(ImNodesEditorContext& editor, const int link_idx) cubic_bezier.P2, cubic_bezier.P3, link_color, - GImNodes->Style.LinkThickness * editor.MiniMapScaling, + GImNodes->Style.LinkThickness * editor.MiniMapScaling / editor.ZoomScale, cubic_bezier.NumSegments); } @@ -1885,7 +1930,12 @@ static void MiniMapUpdate() mini_map_rect.Min, mini_map_rect.Max, mini_map_background); GImNodes->CanvasDrawList->AddRect( - mini_map_rect.Min, mini_map_rect.Max, GImNodes->Style.Colors[ImNodesCol_MiniMapOutline]); + mini_map_rect.Min, + mini_map_rect.Max, + GImNodes->Style.Colors[ImNodesCol_MiniMapOutline], + 0, + 0, + 1 / editor.ZoomScale); // Clip draw list items to mini-map rect (after drawing background/outline) GImNodes->CanvasDrawList->PushClipRect( @@ -1915,7 +1965,8 @@ static void MiniMapUpdate() const ImRect rect = ScreenSpaceToMiniMapSpace(editor, GImNodes->CanvasRectScreenSpace); GImNodes->CanvasDrawList->AddRectFilled(rect.Min, rect.Max, canvas_color); - GImNodes->CanvasDrawList->AddRect(rect.Min, rect.Max, outline_color); + GImNodes->CanvasDrawList->AddRect( + rect.Min, rect.Max, outline_color, 0, 0, 1 / editor.ZoomScale); } // Have to pop mini-map clip rect @@ -2220,37 +2271,68 @@ void BeginNodeEditor() GImNodes->ImNodesUIState = ImNodesUIState_None; - GImNodes->MousePos = ImGui::GetIO().MousePos; - GImNodes->LeftMouseClicked = ImGui::IsMouseClicked(0); - GImNodes->LeftMouseReleased = ImGui::IsMouseReleased(0); - GImNodes->LeftMouseDragging = ImGui::IsMouseDragging(0, 0.0f); - GImNodes->AltMouseClicked = - (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && - *GImNodes->Io.EmulateThreeButtonMouse.Modifier && GImNodes->LeftMouseClicked) || - ImGui::IsMouseClicked(GImNodes->Io.AltMouseButton); - GImNodes->AltMouseDragging = - (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && GImNodes->LeftMouseDragging && - (*GImNodes->Io.EmulateThreeButtonMouse.Modifier)) || - ImGui::IsMouseDragging(GImNodes->Io.AltMouseButton, 0.0f); - GImNodes->AltMouseScrollDelta = ImGui::GetIO().MouseWheel; - GImNodes->MultipleSelectModifier = - (GImNodes->Io.MultipleSelectModifier.Modifier != NULL - ? *GImNodes->Io.MultipleSelectModifier.Modifier - : ImGui::GetIO().KeyCtrl); - GImNodes->ActiveAttribute = false; + GImNodes->IsHovered = false; ImGui::BeginGroup(); { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.f, 1.f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f)); - ImGui::PushStyleColor(ImGuiCol_ChildBg, GImNodes->Style.Colors[ImNodesCol_GridBackground]); - ImGui::BeginChild( - "scrolling_region", - ImVec2(0.f, 0.f), - true, - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollWithMouse); + // Setup zoom context + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); + GImNodes->CanvasOriginalOrigin = ImGui::GetCursorScreenPos(); + GImNodes->OriginalImgCtx = ImGui::GetCurrentContext(); + + // Button to capture mouse events and hover test + ImGui::InvisibleButton("canvas_no_drag", canvas_size); + + ImGuiConfigFlags configFlags = ImGui::GetIO().ConfigFlags; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoNavInputs; + + if (ImGui::IsItemHovered()) + { + GImNodes->IsHovered = true; + } + else + { + windowFlags |= ImGuiWindowFlags_NoInputs; + configFlags &= ImGuiConfigFlags_NoMouse; + } + + const ImGuiStyle& orig_style = ImGui::GetStyle(); + const float delta_time = ImGui::GetIO().DeltaTime; + ImGui::SetCurrentContext(GImNodes->ZoomImgCtx); + ImGuiStyle& new_style = ImGui::GetStyle(); + new_style = orig_style; + ImGui::GetIO().DeltaTime = delta_time; + + // Copy IO events + GImNodes->ZoomImgCtx->InputEventsQueue = GImNodes->OriginalImgCtx->InputEventsTrail; + for (ImGuiInputEvent& e : GImNodes->ZoomImgCtx->InputEventsQueue) + { + if (e.Type == ImGuiInputEventType_MousePos) + { + e.MousePos.PosX = + (e.MousePos.PosX - GImNodes->CanvasOriginalOrigin.x) / editor.ZoomScale; + e.MousePos.PosY = + (e.MousePos.PosY - GImNodes->CanvasOriginalOrigin.y) / editor.ZoomScale; + } + } + + ImGui::GetIO().DisplaySize = canvas_size / editor.ZoomScale; + ImGui::GetIO().ConfigInputTrickleEventQueue = false; + ImGui::GetIO().ConfigFlags = configFlags; + ImGui::NewFrame(); + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, GImNodes->Style.Colors[ImNodesCol_GridBackground]); + ImGui::Begin("editor_canvas", nullptr, windowFlags); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + GImNodes->CanvasOriginScreenSpace = ImGui::GetCursorScreenPos(); // NOTE: we have to fetch the canvas draw list *after* we call @@ -2259,16 +2341,34 @@ void BeginNodeEditor() DrawListSet(ImGui::GetWindowDrawList()); { - const ImVec2 canvas_size = ImGui::GetWindowSize(); + const ImVec2 window_size = ImGui::GetWindowSize(); GImNodes->CanvasRectScreenSpace = ImRect( - EditorSpaceToScreenSpace(ImVec2(0.f, 0.f)), EditorSpaceToScreenSpace(canvas_size)); + EditorSpaceToScreenSpace(ImVec2(0.f, 0.f)), EditorSpaceToScreenSpace(window_size)); if (GImNodes->Style.Flags & ImNodesStyleFlags_GridLines) { - DrawGrid(editor, canvas_size); + DrawGrid(editor, window_size); } } } + + GImNodes->MousePos = ImGui::GetIO().MousePos; + GImNodes->LeftMouseClicked = ImGui::IsMouseClicked(0); + GImNodes->LeftMouseReleased = ImGui::IsMouseReleased(0); + GImNodes->LeftMouseDragging = ImGui::IsMouseDragging(0, 0.0f); + GImNodes->AltMouseClicked = + (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && + *GImNodes->Io.EmulateThreeButtonMouse.Modifier && GImNodes->LeftMouseClicked) || + ImGui::IsMouseClicked(GImNodes->Io.AltMouseButton); + GImNodes->AltMouseDragging = + (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && GImNodes->LeftMouseDragging && + (*GImNodes->Io.EmulateThreeButtonMouse.Modifier)) || + ImGui::IsMouseDragging(GImNodes->Io.AltMouseButton, 0.0f); + GImNodes->AltMouseScrollDelta = ImGui::GetIO().MouseWheel; + GImNodes->MultipleSelectModifier = + (GImNodes->Io.MultipleSelectModifier.Modifier != NULL + ? *GImNodes->Io.MultipleSelectModifier.Modifier + : ImGui::GetIO().KeyCtrl); } void EndNodeEditor() @@ -2413,11 +2513,28 @@ void EndNodeEditor() // Finally, merge the draw channels GImNodes->CanvasDrawList->ChannelsMerge(); - // pop style - ImGui::EndChild(); // end scrolling region - ImGui::PopStyleColor(); // pop child window background color - ImGui::PopStyleVar(); // pop window padding - ImGui::PopStyleVar(); // pop frame padding + GImNodes->OriginalImgCtx->WantTextInputNextFrame = ImMax( + GImNodes->OriginalImgCtx->WantTextInputNextFrame, + GImNodes->ZoomImgCtx->WantTextInputNextFrame); + + if (MouseInCanvas()) + { + GImNodes->OriginalImgCtx->MouseCursor = GImNodes->ZoomImgCtx->MouseCursor; + } + + // End frame for zoom context + ImGui::End(); + ImGui::Render(); + + ImDrawData* draw_data = ImGui::GetDrawData(); + + ImGui::SetCurrentContext(GImNodes->OriginalImgCtx); + GImNodes->OriginalImgCtx = nullptr; + + // Copy draw data over to original context + for (int i = 0; i < draw_data->CmdListsCount; ++i) + AppendDrawData(draw_data->CmdLists[i], GImNodes->CanvasOriginalOrigin, editor.ZoomScale); + ImGui::EndGroup(); } @@ -2792,6 +2909,24 @@ void SnapNodeToGrid(int node_id) node.Origin = SnapOriginToGrid(node.Origin); } +float EditorContextGetZoom() { return EditorContextGet().ZoomScale; } + +void EditorContextSetZoom(float zoom_scale, ImVec2 zoom_centering_pos) +{ + IM_ASSERT(GImNodes->CurrentScope == ImNodesScope_None); + + ImNodesEditorContext& editor = EditorContextGet(); + const float new_zoom = ImMax(0.1f, ImMin(10.0f, zoom_scale)); + + zoom_centering_pos -= GImNodes->CanvasOriginalOrigin; + editor.Panning += zoom_centering_pos / new_zoom - zoom_centering_pos / editor.ZoomScale; + + // Fix mouse position + GImNodes->ZoomImgCtx->IO.MousePos *= editor.ZoomScale / new_zoom; + + editor.ZoomScale = new_zoom; +} + bool IsEditorHovered() { return MouseInCanvas(); } bool IsNodeHovered(int* const node_id) diff --git a/imnodes.h b/imnodes.h index 15ad5ed..b3515f5 100644 --- a/imnodes.h +++ b/imnodes.h @@ -278,6 +278,10 @@ void MiniMap( const ImNodesMiniMapNodeHoveringCallback node_hovering_callback = NULL, const ImNodesMiniMapNodeHoveringCallbackUserData node_hovering_callback_data = NULL); +// Editor zoom controls +float EditorContextGetZoom(); +void EditorContextSetZoom(float zoom_scale, ImVec2 zoom_center); + // Use PushColorStyle and PopColorStyle to modify ImNodesStyle::Colors mid-frame. void PushColorStyle(ImNodesCol item, unsigned int color); void PopColorStyle(); diff --git a/imnodes_internal.h b/imnodes_internal.h index 8cfb5d7..9d05643 100644 --- a/imnodes_internal.h +++ b/imnodes_internal.h @@ -252,6 +252,7 @@ struct ImNodesEditorContext ImVector NodeDepthOrder; // ui related fields + float ZoomScale; ImVec2 Panning; ImVec2 AutoPanningDelta; // Minimum and maximum extents of all content in grid space. Valid after final @@ -283,13 +284,11 @@ struct ImNodesEditorContext float MiniMapScaling; ImNodesEditorContext() - : Nodes(), Pins(), Links(), Panning(0.f, 0.f), SelectedNodeIndices(), SelectedLinkIndices(), - SelectedNodeOffsets(), PrimaryNodeOffset(0.f, 0.f), ClickInteraction(), - MiniMapEnabled(false), MiniMapSizeFraction(0.0f), + : Nodes(), Pins(), Links(), ZoomScale(1.f), Panning(0.f, 0.f), SelectedNodeIndices(), + SelectedLinkIndices(), SelectedNodeOffsets(), PrimaryNodeOffset(0.f, 0.f), + ClickInteraction(), MiniMapEnabled(false), MiniMapSizeFraction(0.0f), MiniMapNodeHoveringCallback(NULL), MiniMapNodeHoveringCallbackUserData(NULL), - MiniMapScaling(0.0f) - { - } + MiniMapScaling(0.0f) {} }; struct ImNodesContext @@ -298,6 +297,8 @@ struct ImNodesContext ImNodesEditorContext* EditorCtx; // Canvas draw list and helper state + ImGuiContext* ZoomImgCtx; + ImGuiContext* OriginalImgCtx; ImDrawList* CanvasDrawList; ImGuiStorage NodeIdxToSubmissionIdx; ImVector NodeIdxSubmissionOrder; @@ -305,6 +306,7 @@ struct ImNodesContext ImVector OccludedPinIndices; // Canvas extents + ImVec2 CanvasOriginalOrigin; ImVec2 CanvasOriginScreenSpace; ImRect CanvasRectScreenSpace; @@ -344,6 +346,7 @@ struct ImNodesContext // ImGui::IO cache ImVec2 MousePos; + bool IsHovered; bool LeftMouseClicked; bool LeftMouseReleased; From 9d89b3e98c91ba2b414c9ad1cdf7e9c48510c6f0 Mon Sep 17 00:00:00 2001 From: Auburn Date: Sun, 4 Feb 2024 16:28:28 +0000 Subject: [PATCH 5/9] Copy all of the ImGui IO config section into zoom context Fixes difference between contexts due to missing config --- imnodes.cpp | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/imnodes.cpp b/imnodes.cpp index 55710a5..a56a729 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -2279,16 +2279,29 @@ void BeginNodeEditor() // Setup zoom context ImVec2 canvas_size = ImGui::GetContentRegionAvail(); GImNodes->CanvasOriginalOrigin = ImGui::GetCursorScreenPos(); - GImNodes->OriginalImgCtx = ImGui::GetCurrentContext(); - - // Button to capture mouse events and hover test - ImGui::InvisibleButton("canvas_no_drag", canvas_size); - - ImGuiConfigFlags configFlags = ImGui::GetIO().ConfigFlags; + GImNodes->OriginalImgCtx = ImGui::GetCurrentContext(); + + // Copy config settings in IO from main context, avoiding input fields + memcpy( + &GImNodes->ZoomImgCtx->IO, + &GImNodes->OriginalImgCtx->IO, + offsetof(ImGuiIO, SetPlatformImeDataFn) + + sizeof(GImNodes->OriginalImgCtx->IO.SetPlatformImeDataFn)); + + GImNodes->ZoomImgCtx->IO.IniFilename = nullptr; + GImNodes->ZoomImgCtx->IO.ConfigInputTrickleEventQueue = false; + GImNodes->ZoomImgCtx->IO.DisplaySize = canvas_size / editor.ZoomScale; + GImNodes->ZoomImgCtx->Style = GImNodes->OriginalImgCtx->Style; + + // Nav (tabbing) needs to be disabled otherwise it doubles up with the main context + // not sure how to get this working correctly ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoNavInputs; + // Button to capture mouse events and hover test + ImGui::InvisibleButton("canvas_no_drag", canvas_size); + if (ImGui::IsItemHovered()) { GImNodes->IsHovered = true; @@ -2296,16 +2309,9 @@ void BeginNodeEditor() else { windowFlags |= ImGuiWindowFlags_NoInputs; - configFlags &= ImGuiConfigFlags_NoMouse; + GImNodes->ZoomImgCtx->IO.ConfigFlags &= ImGuiConfigFlags_NoMouse; } - const ImGuiStyle& orig_style = ImGui::GetStyle(); - const float delta_time = ImGui::GetIO().DeltaTime; - ImGui::SetCurrentContext(GImNodes->ZoomImgCtx); - ImGuiStyle& new_style = ImGui::GetStyle(); - new_style = orig_style; - ImGui::GetIO().DeltaTime = delta_time; - // Copy IO events GImNodes->ZoomImgCtx->InputEventsQueue = GImNodes->OriginalImgCtx->InputEventsTrail; for (ImGuiInputEvent& e : GImNodes->ZoomImgCtx->InputEventsQueue) @@ -2319,9 +2325,7 @@ void BeginNodeEditor() } } - ImGui::GetIO().DisplaySize = canvas_size / editor.ZoomScale; - ImGui::GetIO().ConfigInputTrickleEventQueue = false; - ImGui::GetIO().ConfigFlags = configFlags; + ImGui::SetCurrentContext(GImNodes->ZoomImgCtx); ImGui::NewFrame(); ImGui::SetNextWindowPos(ImVec2(0, 0)); @@ -2352,6 +2356,7 @@ void BeginNodeEditor() } } + // Cache inputs GImNodes->MousePos = ImGui::GetIO().MousePos; GImNodes->LeftMouseClicked = ImGui::IsMouseClicked(0); GImNodes->LeftMouseReleased = ImGui::IsMouseReleased(0); From 162f2b7228cd27f0b6604a2be93a4817f7f95f0d Mon Sep 17 00:00:00 2001 From: Auburn Date: Mon, 5 Feb 2024 00:50:23 +0000 Subject: [PATCH 6/9] Fix GCC compile --- imnodes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imnodes.cpp b/imnodes.cpp index a56a729..85edbbd 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -2283,8 +2283,8 @@ void BeginNodeEditor() // Copy config settings in IO from main context, avoiding input fields memcpy( - &GImNodes->ZoomImgCtx->IO, - &GImNodes->OriginalImgCtx->IO, + (void*)&GImNodes->ZoomImgCtx->IO, + (void*)&GImNodes->OriginalImgCtx->IO, offsetof(ImGuiIO, SetPlatformImeDataFn) + sizeof(GImNodes->OriginalImgCtx->IO.SetPlatformImeDataFn)); From 32e2136a0f79ab96537088b8a757618a90bf8785 Mon Sep 17 00:00:00 2001 From: Auburn Date: Mon, 5 Feb 2024 01:03:03 +0000 Subject: [PATCH 7/9] Fix MSVC error --- imnodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imnodes.cpp b/imnodes.cpp index 85edbbd..202a274 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -1365,7 +1365,7 @@ inline void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) } for (int i = 0, c = src->IdxBuffer.size(); i < c; ++i) { - dl->_IdxWritePtr[i] = idx_read[i] + vtx_start; + dl->_IdxWritePtr[i] = idx_read[i] + (ImDrawIdx)vtx_start; } for (int i = 0, c = src->CmdBuffer.size(); i < c; ++i) { From 4ccaf656b09fd6b69bdac36f2532756760bd0aa3 Mon Sep 17 00:00:00 2001 From: Auburn Date: Fri, 9 Feb 2024 20:44:07 +0000 Subject: [PATCH 8/9] NodeEditor ImGui context accessor, better hover detection --- imnodes.cpp | 50 +++++++++++++++++++++++----------------------- imnodes.h | 4 ++++ imnodes_internal.h | 2 +- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/imnodes.cpp b/imnodes.cpp index 202a274..7dde0f4 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -1346,7 +1346,6 @@ void DrawGrid(ImNodesEditorContext& editor, const ImVec2& canvas_size) inline void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) { - // TODO optimize if vtx_start == 0 || if idx_start == 0 ImDrawList* dl = ImGui::GetWindowDrawList(); const int vtx_start = dl->VtxBuffer.size(); const int idx_start = dl->IdxBuffer.size(); @@ -1371,7 +1370,6 @@ inline void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) { ImDrawCmd cmd = src->CmdBuffer[i]; cmd.IdxOffset += idx_start; - IM_ASSERT(cmd.VtxOffset == 0); cmd.ClipRect.x = cmd.ClipRect.x * scale + origin.x; cmd.ClipRect.y = cmd.ClipRect.y * scale + origin.y; cmd.ClipRect.z = cmd.ClipRect.z * scale + origin.x; @@ -1716,8 +1714,8 @@ void EndPinAttribute() void Initialize(ImNodesContext* context) { - context->ZoomImgCtx = ImGui::CreateContext(ImGui::GetIO().Fonts); - context->ZoomImgCtx->IO.IniFilename = nullptr; + context->NodeEditorImgCtx = ImGui::CreateContext(ImGui::GetIO().Fonts); + context->NodeEditorImgCtx->IO.IniFilename = nullptr; context->OriginalImgCtx = nullptr; context->CanvasOriginalOrigin = ImVec2(0.0f, 0.0f); @@ -1740,7 +1738,7 @@ void Initialize(ImNodesContext* context) void Shutdown(ImNodesContext* ctx) { EditorContextFree(ctx->DefaultEditorCtx); - ImGui::DestroyContext(ctx->ZoomImgCtx); + ImGui::DestroyContext(ctx->NodeEditorImgCtx); } // [SECTION] minimap @@ -2109,6 +2107,8 @@ void EditorContextMoveToNode(const int node_id) editor.Panning.y = -node.Origin.y; } +ImGuiContext* GetNodeEditorImGuiContext() { return GImNodes->NodeEditorImgCtx; } + void SetImGuiContext(ImGuiContext* ctx) { ImGui::SetCurrentContext(ctx); } ImNodesIO& GetIO() { return GImNodes->Io; } @@ -2279,42 +2279,41 @@ void BeginNodeEditor() // Setup zoom context ImVec2 canvas_size = ImGui::GetContentRegionAvail(); GImNodes->CanvasOriginalOrigin = ImGui::GetCursorScreenPos(); - GImNodes->OriginalImgCtx = ImGui::GetCurrentContext(); + GImNodes->OriginalImgCtx = ImGui::GetCurrentContext(); // Copy config settings in IO from main context, avoiding input fields memcpy( - (void*)&GImNodes->ZoomImgCtx->IO, + (void*)&GImNodes->NodeEditorImgCtx->IO, (void*)&GImNodes->OriginalImgCtx->IO, offsetof(ImGuiIO, SetPlatformImeDataFn) + sizeof(GImNodes->OriginalImgCtx->IO.SetPlatformImeDataFn)); - GImNodes->ZoomImgCtx->IO.IniFilename = nullptr; - GImNodes->ZoomImgCtx->IO.ConfigInputTrickleEventQueue = false; - GImNodes->ZoomImgCtx->IO.DisplaySize = canvas_size / editor.ZoomScale; - GImNodes->ZoomImgCtx->Style = GImNodes->OriginalImgCtx->Style; + GImNodes->NodeEditorImgCtx->IO.IniFilename = nullptr; + GImNodes->NodeEditorImgCtx->IO.ConfigInputTrickleEventQueue = false; + GImNodes->NodeEditorImgCtx->IO.DisplaySize = canvas_size / editor.ZoomScale; + GImNodes->NodeEditorImgCtx->Style = GImNodes->OriginalImgCtx->Style; // Nav (tabbing) needs to be disabled otherwise it doubles up with the main context // not sure how to get this working correctly - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoNavInputs; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoMove; // Button to capture mouse events and hover test - ImGui::InvisibleButton("canvas_no_drag", canvas_size); + ImGui::BeginChild("canvas_no_drag", canvas_size, 0, windowFlags); - if (ImGui::IsItemHovered()) + if (ImGui::IsWindowHovered()) { GImNodes->IsHovered = true; } else { windowFlags |= ImGuiWindowFlags_NoInputs; - GImNodes->ZoomImgCtx->IO.ConfigFlags &= ImGuiConfigFlags_NoMouse; + GImNodes->NodeEditorImgCtx->IO.ConfigFlags |= ImGuiConfigFlags_NoMouse; } // Copy IO events - GImNodes->ZoomImgCtx->InputEventsQueue = GImNodes->OriginalImgCtx->InputEventsTrail; - for (ImGuiInputEvent& e : GImNodes->ZoomImgCtx->InputEventsQueue) + GImNodes->NodeEditorImgCtx->InputEventsQueue = GImNodes->OriginalImgCtx->InputEventsTrail; + for (ImGuiInputEvent& e : GImNodes->NodeEditorImgCtx->InputEventsQueue) { if (e.Type == ImGuiInputEventType_MousePos) { @@ -2325,7 +2324,7 @@ void BeginNodeEditor() } } - ImGui::SetCurrentContext(GImNodes->ZoomImgCtx); + ImGui::SetCurrentContext(GImNodes->NodeEditorImgCtx); ImGui::NewFrame(); ImGui::SetNextWindowPos(ImVec2(0, 0)); @@ -2520,11 +2519,11 @@ void EndNodeEditor() GImNodes->OriginalImgCtx->WantTextInputNextFrame = ImMax( GImNodes->OriginalImgCtx->WantTextInputNextFrame, - GImNodes->ZoomImgCtx->WantTextInputNextFrame); + GImNodes->NodeEditorImgCtx->WantTextInputNextFrame); if (MouseInCanvas()) { - GImNodes->OriginalImgCtx->MouseCursor = GImNodes->ZoomImgCtx->MouseCursor; + GImNodes->OriginalImgCtx->MouseCursor = GImNodes->NodeEditorImgCtx->MouseCursor; } // End frame for zoom context @@ -2536,11 +2535,12 @@ void EndNodeEditor() ImGui::SetCurrentContext(GImNodes->OriginalImgCtx); GImNodes->OriginalImgCtx = nullptr; + ImGui::EndChild(); + ImGui::EndGroup(); + // Copy draw data over to original context for (int i = 0; i < draw_data->CmdListsCount; ++i) AppendDrawData(draw_data->CmdLists[i], GImNodes->CanvasOriginalOrigin, editor.ZoomScale); - - ImGui::EndGroup(); } void MiniMap( @@ -2927,7 +2927,7 @@ void EditorContextSetZoom(float zoom_scale, ImVec2 zoom_centering_pos) editor.Panning += zoom_centering_pos / new_zoom - zoom_centering_pos / editor.ZoomScale; // Fix mouse position - GImNodes->ZoomImgCtx->IO.MousePos *= editor.ZoomScale / new_zoom; + GImNodes->NodeEditorImgCtx->IO.MousePos *= editor.ZoomScale / new_zoom; editor.ZoomScale = new_zoom; } diff --git a/imnodes.h b/imnodes.h index b3515f5..4ea178d 100644 --- a/imnodes.h +++ b/imnodes.h @@ -255,6 +255,10 @@ ImVec2 EditorContextGetPanning(); void EditorContextResetPanning(const ImVec2& pos); void EditorContextMoveToNode(const int node_id); +// Get the separate ImGui context that ImNodes uses for zoom functionality +// this is the active context when between BeginNodeEditor and EndNodeEditor +ImGuiContext* GetNodeEditorImGuiContext(); + ImNodesIO& GetIO(); // Returns the global style struct. See the struct declaration for default values. diff --git a/imnodes_internal.h b/imnodes_internal.h index 9d05643..f1c44e1 100644 --- a/imnodes_internal.h +++ b/imnodes_internal.h @@ -297,7 +297,7 @@ struct ImNodesContext ImNodesEditorContext* EditorCtx; // Canvas draw list and helper state - ImGuiContext* ZoomImgCtx; + ImGuiContext* NodeEditorImgCtx; ImGuiContext* OriginalImgCtx; ImDrawList* CanvasDrawList; ImGuiStorage NodeIdxToSubmissionIdx; From 26b70c528d48beeb839035f3da71550f8b0adfa7 Mon Sep 17 00:00:00 2001 From: Auburn Date: Tue, 12 Mar 2024 21:30:36 +0000 Subject: [PATCH 9/9] Fix some ImGui asserts under certain conditions --- imnodes.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imnodes.cpp b/imnodes.cpp index 35a7546..31c9763 100644 --- a/imnodes.cpp +++ b/imnodes.cpp @@ -2288,9 +2288,11 @@ void BeginNodeEditor() offsetof(ImGuiIO, SetPlatformImeDataFn) + sizeof(GImNodes->OriginalImgCtx->IO.SetPlatformImeDataFn)); + GImNodes->NodeEditorImgCtx->IO.BackendPlatformUserData = nullptr; + GImNodes->NodeEditorImgCtx->IO.BackendRendererUserData = nullptr; GImNodes->NodeEditorImgCtx->IO.IniFilename = nullptr; GImNodes->NodeEditorImgCtx->IO.ConfigInputTrickleEventQueue = false; - GImNodes->NodeEditorImgCtx->IO.DisplaySize = canvas_size / editor.ZoomScale; + GImNodes->NodeEditorImgCtx->IO.DisplaySize = ImMax(canvas_size / editor.ZoomScale, ImVec2(0, 0)); GImNodes->NodeEditorImgCtx->Style = GImNodes->OriginalImgCtx->Style; // Nav (tabbing) needs to be disabled otherwise it doubles up with the main context