diff --git a/EvoEngine_SDK/include/Utilities/NodeGraph.hpp b/EvoEngine_SDK/include/Utilities/NodeGraph.hpp index d8638bb0..583cdf91 100644 --- a/EvoEngine_SDK/include/Utilities/NodeGraph.hpp +++ b/EvoEngine_SDK/include/Utilities/NodeGraph.hpp @@ -827,6 +827,12 @@ bool NodeGraph::OnInspect( } ImNodes::MiniMap(); ImNodes::EndNodeEditor(); + + if (ImNodes::IsEditorHovered() && ImGui::GetIO().MouseWheel != 0) { + const float zoom = ImNodes::EditorContextGetZoom() + ImGui::GetIO().MouseWheel * 0.1f; + ImNodes::EditorContextSetZoom(zoom, ImGui::GetMousePos()); + } + NodeGraphNodeHandle hovered_node_handle = -1; NodeGraphLinkHandle hovered_link_handle = -1; NodeGraphInputPinHandle hovered_input_pin_handle = -1; @@ -893,6 +899,11 @@ void NodeGraph::Serialize(YAML::Emitter& out, if (editor_layer) { prev_editor_context = ImNodes::GetCurrentContext()->EditorCtx; ImNodes::EditorContextSet(const_cast(&editor_context_)); + + out << YAML::Key << "ZoomScale" << YAML::Value << editor_context_.ZoomScale; + out << YAML::Key << "Panning" << YAML::Value << glm::vec2(editor_context_.Panning.x, editor_context_.Panning.y); + out << YAML::Key << "AutoPanningDelta" << YAML::Value + << glm::vec2(editor_context_.AutoPanningDelta.x, editor_context_.AutoPanningDelta.y); } std::unordered_map output_pin_map; @@ -1022,6 +1033,17 @@ void NodeGraph::Deserialize(const YAML::Node& in, if (editor_layer) { prev_editor_context = ImNodes::GetCurrentContext()->EditorCtx; ImNodes::EditorContextSet(const_cast(&editor_context_)); + if (in["ZoomScale"]) { + editor_context_.ZoomScale = in["ZoomScale"].as(); + } + if (in["Panning"]) { + const auto t = in["Panning"].as(); + editor_context_.Panning = ImVec2(t.x, t.y); + } + if (in["AutoPanningDelta"]) { + const auto t = in["AutoPanningDelta"].as(); + editor_context_.Panning = ImVec2(t.x, t.y); + } } nodes_.clear(); diff --git a/EvoEngine_SDK/include/Utilities/imnodes.hpp b/EvoEngine_SDK/include/Utilities/imnodes.hpp index 0c069883..755ad762 100644 --- a/EvoEngine_SDK/include/Utilities/imnodes.hpp +++ b/EvoEngine_SDK/include/Utilities/imnodes.hpp @@ -243,6 +243,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. @@ -265,6 +269,10 @@ void MiniMap(const float minimap_size_fraction = 0.2f, 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/EvoEngine_SDK/include/Utilities/imnodes_internal.hpp b/EvoEngine_SDK/include/Utilities/imnodes_internal.hpp index 8e1b047b..dd361469 100644 --- a/EvoEngine_SDK/include/Utilities/imnodes_internal.hpp +++ b/EvoEngine_SDK/include/Utilities/imnodes_internal.hpp @@ -2,7 +2,7 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include -#include "imgui_internal.h" +#include #include "imnodes.hpp" @@ -248,6 +248,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 @@ -282,6 +283,7 @@ struct ImNodesEditorContext { : Nodes(), Pins(), Links(), + ZoomScale(1.f), Panning(0.f, 0.f), SelectedNodeIndices(), SelectedLinkIndices(), @@ -301,6 +303,8 @@ struct ImNodesContext { ImNodesEditorContext* EditorCtx; // Canvas draw list and helper state + ImGuiContext* NodeEditorImgCtx; + ImGuiContext* OriginalImgCtx; ImDrawList* CanvasDrawList; ImGuiStorage NodeIdxToSubmissionIdx; ImVector NodeIdxSubmissionOrder; @@ -308,6 +312,7 @@ struct ImNodesContext { ImVector OccludedPinIndices; // Canvas extents + ImVec2 CanvasOriginalOrigin; ImVec2 CanvasOriginScreenSpace; ImRect CanvasRectScreenSpace; @@ -347,6 +352,7 @@ struct ImNodesContext { // ImGui::IO cache ImVec2 MousePos; + bool IsHovered; bool LeftMouseClicked; bool LeftMouseReleased; diff --git a/EvoEngine_SDK/src/imnodes.cpp b/EvoEngine_SDK/src/imnodes.cpp index e7f5d277..8c0f758b 100644 --- a/EvoEngine_SDK/src/imnodes.cpp +++ b/EvoEngine_SDK/src/imnodes.cpp @@ -454,10 +454,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) { @@ -843,7 +840,7 @@ void ClickInteractionUpdate(ImNodesEditorContext& editor) { GImNodes->CanvasDrawList->AddBezierCubic( #endif cubic_bezier.P0, cubic_bezier.P1, cubic_bezier.P2, cubic_bezier.P3, GImNodes->Style.Colors[ImNodesCol_Link], - GImNodes->Style.LinkThickness, cubic_bezier.NumSegments); + GImNodes->Style.LinkThickness / editor.ZoomScale, cubic_bezier.NumSegments); const bool link_creation_on_snap = GImNodes->HoveredPinIdx.HasValue() && @@ -1083,6 +1080,40 @@ 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; }; @@ -1281,8 +1312,8 @@ void DrawLink(ImNodesEditorContext& editor, const int link_idx) { #else GImNodes->CanvasDrawList->AddBezierCubic( #endif - cubic_bezier.P0, cubic_bezier.P1, cubic_bezier.P2, cubic_bezier.P3, link_color, GImNodes->Style.LinkThickness, - cubic_bezier.NumSegments); + cubic_bezier.P0, cubic_bezier.P1, cubic_bezier.P2, cubic_bezier.P3, link_color, + GImNodes->Style.LinkThickness / editor.ZoomScale, cubic_bezier.NumSegments); } void BeginPinAttribute(const int id, const ImNodesAttributeType type, const ImNodesPinShape shape, const int node_idx) { @@ -1330,6 +1361,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; @@ -1348,6 +1384,7 @@ void Initialize(ImNodesContext* context) { void Shutdown(ImNodesContext* ctx) { EditorContextFree(ctx->DefaultEditorCtx); + ImGui::DestroyContext(ctx->NodeEditorImgCtx); } // [SECTION] minimap @@ -1365,8 +1402,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 @@ -1448,7 +1485,8 @@ static void MiniMapDrawNode(ImNodesEditorContext& editor, const int node_idx) { GImNodes->CanvasDrawList->AddRectFilled(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); + GImNodes->CanvasDrawList->AddRect(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) { @@ -1479,7 +1517,7 @@ static void MiniMapDrawLink(ImNodesEditorContext& editor, const int link_idx) { GImNodes->CanvasDrawList->AddBezierCubic( #endif cubic_bezier.P0, cubic_bezier.P1, cubic_bezier.P2, cubic_bezier.P3, link_color, - GImNodes->Style.LinkThickness * editor.MiniMapScaling, cubic_bezier.NumSegments); + GImNodes->Style.LinkThickness * editor.MiniMapScaling / editor.ZoomScale, cubic_bezier.NumSegments); } static void MiniMapUpdate() { @@ -1504,7 +1542,7 @@ static void MiniMapUpdate() { GImNodes->CanvasDrawList->AddRectFilled(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]); + 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(mini_map_rect.Min, mini_map_rect.Max, @@ -1530,7 +1568,7 @@ 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 @@ -1678,6 +1716,10 @@ void EditorContextMoveToNode(const int node_id) { editor.Panning.y = -node.Origin.y; } +ImGuiContext* GetNodeEditorImGuiContext() { + return GImNodes->NodeEditorImgCtx; +} + void SetImGuiContext(ImGuiContext* ctx) { ImGui::SetCurrentContext(ctx); } @@ -1836,30 +1878,66 @@ 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, PlatformSetImeDataFn) + sizeof(GImNodes->OriginalImgCtx->IO.PlatformSetImeDataFn)); + + 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; + GImNodes->NodeEditorImgCtx->IO.ConfigFlags -= ImGuiConfigFlags_ViewportsEnable; + GImNodes->NodeEditorImgCtx->IO.ConfigFlags -= ImGuiConfigFlags_DockingEnable; + + // 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 | + ImGuiWindowFlags_NoBackground; + + // 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 @@ -1868,15 +1946,31 @@ 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)); + ImRect(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() { @@ -2001,12 +2095,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->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(const float minimap_size_fraction, const ImNodesMiniMapLocation location, @@ -2347,6 +2457,25 @@ 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(); }