From 5303b5d81cddf08593efdf1f06a73d2ba6ffedf9 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 8 Jul 2025 19:22:25 +0200 Subject: [PATCH 1/3] feat: Initial implementation of the input mapping to support different input mappings --- implot3d.cpp | 69 +++++++++++++++++++++++++++++++++++++---------- implot3d.h | 30 +++++++++++++++++++++ implot3d_demo.cpp | 52 +++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 14 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index e0f2110..ccb6a11 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -28,6 +28,7 @@ // [SECTION] Setup Utils // [SECTION] Miscellaneous // [SECTION] Styles +// [SECTION] Input Mapping // [SECTION] Colormaps // [SECTION] Context Utils // [SECTION] Style Utils @@ -39,6 +40,7 @@ // [SECTION] ImPlot3DAxis // [SECTION] ImPlot3DPlot // [SECTION] ImPlot3DStyle +// [SECTION] ImPlot3DInputMap // [SECTION] Metrics //----------------------------------------------------------------------------- @@ -1934,6 +1936,7 @@ ImPlot3DRay NDCRayToPlotRay(const ImPlot3DRay& ray) { static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f; void HandleInput(ImPlot3DPlot& plot) { + ImPlot3DContext& gp = *GImPlot3D; ImGuiIO& IO = ImGui::GetIO(); // clang-format off @@ -1950,7 +1953,7 @@ void HandleInput(ImPlot3DPlot& plot) { #endif // State - const ImVec2 rot_drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); + const ImVec2 rot_drag = ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.RotateMod) ? ImGui::GetMouseDragDelta(gp.InputMap.Rotate) : ImVec2(0, 0); const bool rotating = ImLengthSqr(rot_drag) > MOUSE_CURSOR_DRAG_THRESHOLD; // Check if any axis/plane is hovered @@ -1974,7 +1977,8 @@ void HandleInput(ImPlot3DPlot& plot) { } // If the user is no longer pressing the translation/zoom buttons, set axes as not held - if (!ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) { + if (!(ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.PanMod) && ImGui::IsMouseDown(gp.InputMap.Pan)) && + !(ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod) && ImGui::IsMouseDown(ImGuiMouseButton_Middle))) { for (int i = 0; i < 3; i++) plot.Axes[i].Held = false; } @@ -2014,7 +2018,8 @@ void HandleInput(ImPlot3DPlot& plot) { ImPlot3DPoint mouse_pos_plot = PixelsToPlotPlane(mouse_pos, mouse_plane, false); // Handle translation/zoom fit with double click - if (plot_clicked && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle))) { + if (plot_clicked && (ImGui::IsMouseDoubleClicked(gp.InputMap.Fit) || + (ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod) && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)))) { plot.FitThisFrame = true; for (int i = 0; i < 3; i++) plot.Axes[i].FitThisFrame = plot.Axes[i].Hovered; @@ -2027,8 +2032,12 @@ void HandleInput(ImPlot3DPlot& plot) { plot.Axes[i].FitThisFrame = true; } + // cancel due to DND activity + if (GImGui->DragDropActive || ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.OverrideMod)) + return; + // Handle translation with right mouse button - if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + if (plot.Held && ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.PanMod) && ImGui::IsMouseDown(gp.InputMap.Pan)) { ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y); if (plot.Axes[0].Hovered && plot.Axes[1].Hovered && plot.Axes[2].Hovered) { @@ -2046,7 +2055,7 @@ void HandleInput(ImPlot3DPlot& plot) { // Adjust delta for inverted axes for (int i = 0; i < 3; i++) { - if (ImHasFlag(plot.Axes[i].Flags, ImPlot3DAxisFlags_Invert)) + if (ImPlot3D::ImHasFlag(plot.Axes[i].Flags, ImPlot3DAxisFlags_Invert)) delta_plot[i] *= -1; } @@ -2094,13 +2103,14 @@ void HandleInput(ImPlot3DPlot& plot) { } // Handle context click with right mouse button - if (plot.Held && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoMenus)) + if (plot.Held && ImGui::IsMouseClicked(gp.InputMap.Menu) && !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoMenus)) plot.ContextClick = true; - if (rotating || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) + if (rotating || (ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.RotateMod) && ImGui::IsMouseDoubleClicked(gp.InputMap.Rotate))) plot.ContextClick = false; // Handle reset rotation with left mouse double click - if (plot.Held && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right) && !plot.IsRotationLocked()) { + if (plot.Held && ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.RotateMod) && ImGui::IsMouseDoubleClicked(gp.InputMap.ResetRotate) && + !plot.IsRotationLocked()) { plot.RotationAnimationEnd = plot.Rotation; // Calculate rotation to align the z-axis with the camera direction @@ -2161,7 +2171,7 @@ void HandleInput(ImPlot3DPlot& plot) { } // Handle rotation with left mouse dragging - if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Right) && !plot.IsRotationLocked()) { + if (plot.Held && ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.RotateMod) && ImGui::IsMouseDown(gp.InputMap.Rotate) && !plot.IsRotationLocked()) { ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y); // Map delta to rotation angles (in radians) @@ -2178,10 +2188,11 @@ void HandleInput(ImPlot3DPlot& plot) { } // Handle zoom with mouse wheel - if (plot.Hovered) { + if (plot.Hovered && ImPlot3D::ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod)) { ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID); - if (ImGui::IsMouseDown(ImGuiMouseButton_Middle) || IO.MouseWheel != 0.0f) { - float delta = ImGui::IsMouseDown(ImGuiMouseButton_Middle) ? (-0.01f * IO.MouseDelta.y) : (-0.1f * IO.MouseWheel); + const bool middle_mouse_button_down = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + if (middle_mouse_button_down || IO.MouseWheel != 0.0f) { + float delta = middle_mouse_button_down ? (-0.01f * IO.MouseDelta.y) : (-gp.InputMap.ZoomRate * IO.MouseWheel); float zoom = 1.0f + delta; for (int i = 0; i < 3; i++) { ImPlot3DAxis& axis = plot.Axes[i]; @@ -2226,8 +2237,8 @@ void HandleInput(ImPlot3DPlot& plot) { } // Handle context menu (should not happen if it is not a double click action) - bool not_double_click = (float)(ImGui::GetTime() - IO.MouseClickedTime[ImGuiMouseButton_Right]) > IO.MouseDoubleClickTime; - if (plot.Hovered && plot.ContextClick && not_double_click && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) { + bool not_double_click = (float)(ImGui::GetTime() - IO.MouseClickedTime[gp.InputMap.Menu]) > IO.MouseDoubleClickTime; + if (plot.Hovered && plot.ContextClick && not_double_click && !ImGui::IsMouseDown(gp.InputMap.Menu)) { plot.ContextClick = false; plot.OpenContextThisFrame = true; } @@ -2546,6 +2557,30 @@ ImVec4 GetStyleColorVec4(ImPlot3DCol idx) { return IsColorAuto(idx) ? GetAutoCol ImU32 GetStyleColorU32(ImPlot3DCol idx) { return ImGui::ColorConvertFloat4ToU32(ImPlot3D::GetStyleColorVec4(idx)); } +//----------------------------------------------------------------------------- +// [Section] Input Mapping +//----------------------------------------------------------------------------- + +ImPlot3DInputMap& GetInputMap() { + IMPLOT3D_CHECK_CTX(); + ImPlot3DContext& gp = *GImPlot3D; + return gp.InputMap; +} + +void MapInputDefault(ImPlot3DInputMap* dst) { + ImPlot3DInputMap& map = dst ? *dst : GetInputMap(); + map.Pan = ImGuiMouseButton_Left; + map.PanMod = ImGuiMod_None; + map.Fit = ImGuiMouseButton_Left; + map.ResetRotate = ImGuiMouseButton_Right; + map.Rotate = ImGuiMouseButton_Right; + map.RotateMod = ImGuiMod_None; + map.Menu = ImGuiMouseButton_Right; + map.OverrideMod = ImGuiMod_Ctrl; + map.ZoomMod = ImGuiMod_None; + map.ZoomRate = 0.1f; +} + //------------------------------------------------------------------------------ // [SECTION] Colormaps //------------------------------------------------------------------------------ @@ -3412,6 +3447,12 @@ ImPlot3DStyle::ImPlot3DStyle() { Colormap = ImPlot3DColormap_Deep; }; +//----------------------------------------------------------------------------- +// [SECTION] ImPlot3DInputMap +//----------------------------------------------------------------------------- + +ImPlot3DInputMap::ImPlot3DInputMap() { ImPlot3D::MapInputDefault(this); } + //----------------------------------------------------------------------------- // [SECTION] Metrics //----------------------------------------------------------------------------- diff --git a/implot3d.h b/implot3d.h index d0e9f08..2f4cbcd 100644 --- a/implot3d.h +++ b/implot3d.h @@ -58,6 +58,7 @@ // Forward declarations struct ImPlot3DContext; struct ImPlot3DStyle; +struct ImPlot3DInputMap; struct ImPlot3DPoint; struct ImPlot3DRay; struct ImPlot3DPlane; @@ -544,6 +545,16 @@ IMPLOT3D_API void SetNextMarkerStyle(ImPlot3DMarker marker = IMPLOT3D_AUTO, floa IMPLOT3D_API ImVec4 GetStyleColorVec4(ImPlot3DCol idx); IMPLOT3D_API ImU32 GetStyleColorU32(ImPlot3DCol idx); +//----------------------------------------------------------------------------- +// [SECTION] Input Mapping +//----------------------------------------------------------------------------- + +// Provides access to input mapping structure for permanant modifications to controls for pan, select, etc. +IMPLOT3D_API ImPlot3DInputMap& GetInputMap(); + +// Default input mapping: pan = LMB drag, box select = RMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll. +IMPLOT3D_API void MapInputDefault(ImPlot3DInputMap* dst = nullptr); + //----------------------------------------------------------------------------- // [SECTION] Colormaps //----------------------------------------------------------------------------- @@ -829,6 +840,25 @@ struct ImPlot3DStyle { ImPlot3DStyle(const ImPlot3DStyle& other) = default; }; +//----------------------------------------------------------------------------- +// [SECTION] ImPlot3DInputMap +//----------------------------------------------------------------------------- + +// Input mapping structure. Default values listed. See also MapInputDefault, MapInputReverse. +struct ImPlot3DInputMap { + ImGuiMouseButton Pan; // LMB enables panning when held, + int PanMod; // none optional modifier that must be held for panning/fitting + ImGuiMouseButton Fit; // LMB initiates fit when double clicked + ImGuiMouseButton ResetRotate; // RMB initiates reset of the rotate when double clicked. When double clicked over the axis change to 2D view of the axis + ImGuiMouseButton Rotate; // RMB rotate the plot + int RotateMod; // none optional modifier that must be held when rotating + ImGuiMouseButton Menu; // RMB opens context menus (if enabled) when clicked + int OverrideMod; // Ctrl when held, all input is ignored; used to enable axis/plots as DND sources + int ZoomMod; // none optional modifier that must be held for scroll wheel zooming + float ZoomRate; // 0.1f zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click); make negative to invert + IMPLOT3D_API ImPlot3DInputMap(); +}; + //----------------------------------------------------------------------------- // [SECTION] Meshes //----------------------------------------------------------------------------- diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 4e47fe3..9165a63 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -790,6 +790,57 @@ void DemoCustomRendering() { } } +void ButtonSelector(const char* label, ImGuiMouseButton* b) { + ImGui::PushID(label); + if (ImGui::RadioButton("LMB", *b == ImGuiMouseButton_Left)) + *b = ImGuiMouseButton_Left; + ImGui::SameLine(); + if (ImGui::RadioButton("RMB", *b == ImGuiMouseButton_Right)) + *b = ImGuiMouseButton_Right; + ImGui::SameLine(); + if (ImGui::RadioButton("MMB", *b == ImGuiMouseButton_Middle)) + *b = ImGuiMouseButton_Middle; + ImGui::PopID(); +} + +void ModSelector(const char* label, int* k) { + ImGui::PushID(label); + ImGui::CheckboxFlags("Ctrl", (unsigned int*)k, ImGuiMod_Ctrl); + ImGui::SameLine(); + ImGui::CheckboxFlags("Shift", (unsigned int*)k, ImGuiMod_Shift); + ImGui::SameLine(); + ImGui::CheckboxFlags("Alt", (unsigned int*)k, ImGuiMod_Alt); + ImGui::SameLine(); + ImGui::CheckboxFlags("Super", (unsigned int*)k, ImGuiMod_Super); + ImGui::PopID(); +} + +void InputMapping(const char* label, ImGuiMouseButton* b, int* k) { + ImGui::LabelText("##", "%s", label); + if (b != nullptr) { + ImGui::SameLine(130); + ButtonSelector(label, b); + } + if (k != nullptr) { + ImGui::SameLine(300); + ModSelector(label, k); + } +} + +void ShowInputMapping() { + ImPlot3DInputMap& map = ImPlot3D::GetInputMap(); + if (ImGui::Button("Reset")) + MapInputDefault(&map); + InputMapping("Pan", &map.Pan, &map.PanMod); + InputMapping("Fit", &map.Fit, nullptr); + InputMapping("Reset Rotate", &map.ResetRotate, nullptr); + InputMapping("Rotate", &map.Rotate, &map.RotateMod); + InputMapping("Menu", &map.Menu, nullptr); + InputMapping("OverrideMod", nullptr, &map.OverrideMod); + InputMapping("ZoomMod", nullptr, &map.ZoomMod); + ImGui::SliderFloat("ZoomRate", &map.ZoomRate, -1, 1); +} + //----------------------------------------------------------------------------- // [SECTION] Demo Window //----------------------------------------------------------------------------- @@ -890,6 +941,7 @@ void ShowAllDemos() { if (ImGui::BeginTabItem("Custom")) { DemoHeader("Custom Styles", DemoCustomStyles); DemoHeader("Custom Rendering", DemoCustomRendering); + DemoHeader("Show Input Mapping", ShowInputMapping); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Help")) { From 1c66fc9e8eed68da1bf2cc4fa1f3ed0ba1628b45 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 8 Jul 2025 19:24:02 +0200 Subject: [PATCH 2/3] fix: Added InputMap to the context --- implot3d_internal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/implot3d_internal.h b/implot3d_internal.h index d1a22be..873868e 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -694,6 +694,7 @@ struct ImPlot3DContext { ImPlot3DItem* CurrentItem; ImPlot3DNextItemData NextItemData; ImPlot3DStyle Style; + ImPlot3DInputMap InputMap; ImVector ColorModifiers; ImVector StyleModifiers; ImVector ColormapModifiers; From 999f6e4f38f283f1c4b1b53fc4ef615421facf5a Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:22:40 +0200 Subject: [PATCH 3/3] fix: Ensure that the `ImPlot3D::MapInputDefault` function is called --- implot3d_demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 9165a63..1602b79 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -830,7 +830,7 @@ void InputMapping(const char* label, ImGuiMouseButton* b, int* k) { void ShowInputMapping() { ImPlot3DInputMap& map = ImPlot3D::GetInputMap(); if (ImGui::Button("Reset")) - MapInputDefault(&map); + ImPlot3D::MapInputDefault(&map); InputMapping("Pan", &map.Pan, &map.PanMod); InputMapping("Fit", &map.Fit, nullptr); InputMapping("Reset Rotate", &map.ResetRotate, nullptr);