diff --git a/CMakeLists.txt b/CMakeLists.txt index c4c8d50..4cf7662 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,4 +104,4 @@ if(IMNODES_EXAMPLES) else() target_link_libraries(hello X11 Xext GL) endif() -endif() +endif() \ No newline at end of file diff --git a/imnodes.cpp b/imnodes.cpp index a77c0a9..31c9763 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,44 @@ void DrawGrid(ImNodesEditorContext& editor, const ImVec2& canvas_size) } } +inline void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) +{ + 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] + (ImDrawIdx)vtx_start; + } + for (int i = 0, c = src->CmdBuffer.size(); i < c; ++i) + { + ImDrawCmd cmd = src->CmdBuffer[i]; + cmd.IdxOffset += idx_start; + 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 +1657,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 +1714,11 @@ void EndPinAttribute() void Initialize(ImNodesContext* context) { + context->NodeEditorImgCtx = ImGui::CreateContext(ImGui::GetIO().Fonts); + context->NodeEditorImgCtx->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 +1735,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->NodeEditorImgCtx); +} // [SECTION] minimap @@ -1717,8 +1760,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 +1857,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 +1897,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 +1928,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 +1963,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 @@ -2058,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; } @@ -2220,37 +2271,73 @@ 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(); + + // Copy config settings in IO from main context, avoiding input fields + memcpy( + (void*)&GImNodes->NodeEditorImgCtx->IO, + (void*)&GImNodes->OriginalImgCtx->IO, + 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 = 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 + // not sure how to get this working correctly + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoMove; + + // Button to capture mouse events and hover test + ImGui::BeginChild("canvas_no_drag", canvas_size, 0, windowFlags); + + if (ImGui::IsWindowHovered()) + { + GImNodes->IsHovered = true; + } + else + { + windowFlags |= ImGuiWindowFlags_NoInputs; + GImNodes->NodeEditorImgCtx->IO.ConfigFlags |= ImGuiConfigFlags_NoMouse; + } + + // Copy IO events + GImNodes->NodeEditorImgCtx->InputEventsQueue = GImNodes->OriginalImgCtx->InputEventsTrail; + for (ImGuiInputEvent& e : GImNodes->NodeEditorImgCtx->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::SetCurrentContext(GImNodes->NodeEditorImgCtx); + 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 +2346,35 @@ 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); } } } + + // Cache inputs + 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,12 +2519,30 @@ 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->NodeEditorImgCtx->WantTextInputNextFrame); + + if (MouseInCanvas()) + { + GImNodes->OriginalImgCtx->MouseCursor = GImNodes->NodeEditorImgCtx->MouseCursor; + } + + // End frame for zoom context + ImGui::End(); + ImGui::Render(); + + ImDrawData* draw_data = ImGui::GetDrawData(); + + 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); } void MiniMap( @@ -2792,6 +2916,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->NodeEditorImgCtx->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..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. @@ -278,6 +282,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 bb1ee8f..5aa11aa 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 @@ -264,7 +265,7 @@ struct ImNodesEditorContext // Relative origins of selected nodes for snapping of dragged nodes ImVector SelectedNodeOffsets; // Offset of the primary node origin relative to the mouse cursor. - ImVec2 PrimaryNodeOffset; + ImVec2 PrimaryNodeOffset; ImClickInteractionState ClickInteraction; @@ -283,8 +284,8 @@ struct ImNodesEditorContext float MiniMapScaling; ImNodesEditorContext() - : Nodes(), Pins(), Links(), Panning(0.f, 0.f), SelectedNodeIndices(), SelectedLinkIndices(), - SelectedNodeOffsets(), PrimaryNodeOffset(0.f, 0.f), ClickInteraction(), + : 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) { @@ -297,6 +298,8 @@ struct ImNodesContext ImNodesEditorContext* EditorCtx; // Canvas draw list and helper state + ImGuiContext* NodeEditorImgCtx; + ImGuiContext* OriginalImgCtx; ImDrawList* CanvasDrawList; ImGuiStorage NodeIdxToSubmissionIdx; ImVector NodeIdxSubmissionOrder; @@ -304,6 +307,7 @@ struct ImNodesContext ImVector OccludedPinIndices; // Canvas extents + ImVec2 CanvasOriginalOrigin; ImVec2 CanvasOriginScreenSpace; ImRect CanvasRectScreenSpace; @@ -343,6 +347,7 @@ struct ImNodesContext // ImGui::IO cache ImVec2 MousePos; + bool IsHovered; bool LeftMouseClicked; bool LeftMouseReleased;