From 24522b28862053a4831bb42a9d4f45cf376c036d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Catal=C3=A3o?= Date: Wed, 16 Sep 2026 22:01:22 +0100 Subject: [PATCH 1/3] fix: DragRect can no longer be clicked outside the plot area --- implot.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/implot.cpp b/implot.cpp index c4485f40..cc14bf1c 100644 --- a/implot.cpp +++ b/implot.cpp @@ -4143,6 +4143,7 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m ImVec2 pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO); ImRect rect(ImMin(p[0],p[2]),ImMax(p[0],p[2])); ImRect rect_grab = rect; rect_grab.Expand(DRAG_GRAB_HALF_SIZE); + rect_grab.ClipWithFull(GImPlot->CurrentPlot->PlotRect); ImGuiMouseCursor cur[4]; if (show_curs) { @@ -4167,6 +4168,7 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m if (input) { // middle point ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE); + b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); clicked = ImGui::ButtonBehavior(b_rect,id,&hovered,&held); if (out_clicked) *out_clicked = clicked; if (out_hovered) *out_hovered = hovered; @@ -4188,6 +4190,7 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m for (int i = 0; i < 4; ++i) { // points ImRect b_rect(p[i].x - DRAG_GRAB_HALF_SIZE, p[i].y - DRAG_GRAB_HALF_SIZE, p[i].x + DRAG_GRAB_HALF_SIZE, p[i].y + DRAG_GRAB_HALF_SIZE); + b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); ImGuiID p_id = id + i + 1; ImGui::KeepAliveID(p_id); if (input) { @@ -4210,6 +4213,7 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m ImVec2 e_max = ImMax(p[i],p[(i+1)%4]); b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE) : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE); + b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); ImGuiID e_id = id + i + 5; ImGui::KeepAliveID(e_id); if (input) { From a85a2cbc158a0d159b5ebd0c6344d7e480d76f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Catal=C3=A3o?= Date: Wed, 16 Sep 2026 22:45:56 +0100 Subject: [PATCH 2/3] fix: DragPoint and DragLines can no longer be clicked outside the plot area --- implot.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/implot.cpp b/implot.cpp index cc14bf1c..d392c703 100644 --- a/implot.cpp +++ b/implot.cpp @@ -3984,6 +3984,7 @@ bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImGui::KeepAliveID(id); if (input) { + rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held); if (out_clicked) *out_clicked = clicked; if (out_hovered) *out_hovered = hovered; @@ -4033,6 +4034,7 @@ bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPl ImGui::KeepAliveID(id); if (input) { + rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held); if (out_clicked) *out_clicked = clicked; if (out_hovered) *out_hovered = hovered; @@ -4089,6 +4091,7 @@ bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPl ImGui::KeepAliveID(id); if (input) { + rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held); if (out_clicked) *out_clicked = clicked; if (out_hovered) *out_hovered = hovered; From 9a390372dfa85d14894f18bb5f3aab452d16f730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Catal=C3=A3o?= Date: Wed, 16 Sep 2026 23:19:00 +0100 Subject: [PATCH 3/3] feat: added NoMove and NoResize flags to ImPlotDragToolFlags --- implot.cpp | 128 +++++++++++++++++++++++++++--------------------- implot.h | 2 + implot_demo.cpp | 10 ++-- 3 files changed, 80 insertions(+), 60 deletions(-) diff --git a/implot.cpp b/implot.cpp index d392c703..df0a487f 100644 --- a/implot.cpp +++ b/implot.cpp @@ -3973,6 +3973,7 @@ bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const bool move_allowed = !ImHasFlag(flags, ImPlotDragToolFlags_NoMove); const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, radius); const ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; const ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); @@ -3992,7 +3993,7 @@ bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, } bool modified = false; - if (held && ImGui::IsMouseDragging(0)) { + if (move_allowed && held && ImGui::IsMouseDragging(0)) { *x = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; *y = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; modified = true; @@ -4024,6 +4025,8 @@ bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPl const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const bool move_allowed = !ImHasFlag(flags, ImPlotDragToolFlags_NoMove); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2); float yt = gp.CurrentPlot->PlotRect.Min.y; float yb = gp.CurrentPlot->PlotRect.Max.y; @@ -4049,7 +4052,7 @@ bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPl ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); bool modified = false; - if (held && ImGui::IsMouseDragging(0)) { + if (move_allowed && held && ImGui::IsMouseDragging(0)) { *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; modified = true; } @@ -4080,6 +4083,8 @@ bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPl const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const bool move_allowed = !ImHasFlag(flags, ImPlotDragToolFlags_NoMove); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2); float xl = gp.CurrentPlot->PlotRect.Min.x; float xr = gp.CurrentPlot->PlotRect.Max.x; @@ -4106,7 +4111,7 @@ bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPl ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); bool modified = false; - if (held && ImGui::IsMouseDragging(0)) { + if (move_allowed && held && ImGui::IsMouseDragging(0)) { *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; modified = true; } @@ -4137,6 +4142,9 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const bool move_allowed = !ImHasFlag(flags, ImPlotDragToolFlags_NoMove); + const bool resize_allowed = !ImHasFlag(flags, ImPlotDragToolFlags_NoResize); + bool h[] = {true,false,true,false}; double* x[] = {x_min,x_max,x_max,x_min}; double* y[] = {y_min,y_min,y_max,y_max}; @@ -4166,7 +4174,7 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m bool clicked = false, hovered = false, held = false; const bool is_movable = *x_min != *x_max || *y_min != *y_max; - if (is_movable) { + if (is_movable && move_allowed) { ImGui::KeepAliveID(id); if (input) { // middle point @@ -4190,58 +4198,60 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m } } - for (int i = 0; i < 4; ++i) { - // points - ImRect b_rect(p[i].x - DRAG_GRAB_HALF_SIZE, p[i].y - DRAG_GRAB_HALF_SIZE, p[i].x + DRAG_GRAB_HALF_SIZE, p[i].y + DRAG_GRAB_HALF_SIZE); - b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); - ImGuiID p_id = id + i + 1; - ImGui::KeepAliveID(p_id); - if (input) { - clicked = ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held); - if (out_clicked) *out_clicked = *out_clicked || clicked; - if (out_hovered) *out_hovered = *out_hovered || hovered; - if (out_held) *out_held = *out_held || held; - } - if ((hovered || held) && show_curs) - ImGui::SetMouseCursor(cur[i]); - - if (held && ImGui::IsMouseDragging(0)) { - *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; - *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; - modified = true; - } + if (resize_allowed) { + for (int i = 0; i < 4; ++i) { + // points + ImRect b_rect(p[i].x - DRAG_GRAB_HALF_SIZE, p[i].y - DRAG_GRAB_HALF_SIZE, p[i].x + DRAG_GRAB_HALF_SIZE, p[i].y + DRAG_GRAB_HALF_SIZE); + b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); + ImGuiID p_id = id + i + 1; + ImGui::KeepAliveID(p_id); + if (input) { + clicked = ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held); + if (out_clicked) *out_clicked = *out_clicked || clicked; + if (out_hovered) *out_hovered = *out_hovered || hovered; + if (out_held) *out_held = *out_held || held; + } + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(cur[i]); - // edges - ImVec2 e_min = ImMin(p[i],p[(i+1)%4]); - ImVec2 e_max = ImMax(p[i],p[(i+1)%4]); - b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE) - : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE); - b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); - ImGuiID e_id = id + i + 5; - ImGui::KeepAliveID(e_id); - if (input) { - clicked = ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held); - if (out_clicked) *out_clicked = *out_clicked || clicked; - if (out_hovered) *out_hovered = *out_hovered || hovered; - if (out_held) *out_held = *out_held || held; - } - if ((hovered || held) && show_curs) - h[i] ? ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); - if (held && ImGui::IsMouseDragging(0)) { - if (h[i]) - *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; - else + if (held && ImGui::IsMouseDragging(0)) { *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; - modified = true; - } - if (hovered && ImGui::IsMouseDoubleClicked(0)) - { - ImPlotRect b = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO); - if (h[i]) - *y[i] = ((y[i] == y_min && *y_min < *y_max) || (y[i] == y_max && *y_max < *y_min)) ? b.Y.Min : b.Y.Max; - else - *x[i] = ((x[i] == x_min && *x_min < *x_max) || (x[i] == x_max && *x_max < *x_min)) ? b.X.Min : b.X.Max; - modified = true; + *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + modified = true; + } + + // edges + ImVec2 e_min = ImMin(p[i],p[(i+1)%4]); + ImVec2 e_max = ImMax(p[i],p[(i+1)%4]); + b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE) + : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE); + b_rect.ClipWithFull(GImPlot->CurrentPlot->PlotRect); + ImGuiID e_id = id + i + 5; + ImGui::KeepAliveID(e_id); + if (input) { + clicked = ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held); + if (out_clicked) *out_clicked = *out_clicked || clicked; + if (out_hovered) *out_hovered = *out_hovered || hovered; + if (out_held) *out_held = *out_held || held; + } + if ((hovered || held) && show_curs) + h[i] ? ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + if (held && ImGui::IsMouseDragging(0)) { + if (h[i]) + *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + else + *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + modified = true; + } + if (hovered && ImGui::IsMouseDoubleClicked(0)) + { + ImPlotRect b = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO); + if (h[i]) + *y[i] = ((y[i] == y_min && *y_min < *y_max) || (y[i] == y_max && *y_max < *y_min)) ? b.Y.Min : b.Y.Max; + else + *x[i] = ((x[i] == x_min && *x_min < *x_max) || (x[i] == x_max && *x_max < *x_min)) ? b.X.Min : b.X.Max; + modified = true; + } } } @@ -4265,9 +4275,13 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m DrawList.AddRectFilled(rect.Min, rect.Max, col32_a); DrawList.AddRect(rect.Min, rect.Max, col32); if (input && (modified || mouse_inside)) { - DrawList.AddCircleFilled(pc,DRAG_GRAB_HALF_SIZE,col32); - for (int i = 0; i < 4; ++i) - DrawList.AddCircleFilled(p[i],DRAG_GRAB_HALF_SIZE,col32); + if (move_allowed) { + DrawList.AddCircleFilled(pc,DRAG_GRAB_HALF_SIZE,col32); + } + if (resize_allowed) { + for (int i = 0; i < 4; ++i) + DrawList.AddCircleFilled(p[i],DRAG_GRAB_HALF_SIZE,col32); + } } PopPlotClipRect(); ImGui::PopID(); diff --git a/implot.h b/implot.h index 6cc5bbbb..a2031682 100644 --- a/implot.h +++ b/implot.h @@ -239,6 +239,8 @@ enum ImPlotDragToolFlags_ { ImPlotDragToolFlags_NoFit = 1 << 1, // the drag tool won't be considered for plot fits ImPlotDragToolFlags_NoInputs = 1 << 2, // lock the tool from user inputs ImPlotDragToolFlags_Delayed = 1 << 3, // tool rendering will be delayed one frame; useful when applying position-constraints + ImPlotDragToolFlags_NoMove = 1 << 4, // lock the tool from user inputs + ImPlotDragToolFlags_NoResize = 1 << 5, // lock the tool from user inputs }; // Flags for ColormapScale diff --git a/implot_demo.cpp b/implot_demo.cpp index 1b92b618..cfa98a74 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -1911,7 +1911,8 @@ void Demo_DragPoints() { static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); - ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); ImGui::SameLine(); + ImGui::CheckboxFlags("NoMove", (unsigned int*)&flags, ImPlotDragToolFlags_NoMove); ImPlotAxisFlags ax_flags = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks; bool clicked[4] = {false, false, false, false}; bool hovered[4] = {false, false, false, false}; @@ -1973,7 +1974,8 @@ void Demo_DragLines() { static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); - ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); ImGui::SameLine(); + ImGui::CheckboxFlags("NoMove", (unsigned int*)&flags, ImPlotDragToolFlags_NoMove); if (ImPlot::BeginPlot("##lines",ImVec2(-1,0))) { ImPlot::SetupAxesLimits(0,1,0,1); ImPlot::DragLineX(0,&x1,ImVec4(1,1,1,1),1,flags); @@ -2019,7 +2021,9 @@ void Demo_DragRects() { static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); - ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); ImGui::SameLine(); + ImGui::CheckboxFlags("NoMove", (unsigned int*)&flags, ImPlotDragToolFlags_NoMove); ImGui::SameLine(); + ImGui::CheckboxFlags("NoResize", (unsigned int*)&flags, ImPlotDragToolFlags_NoResize); if (ImPlot::BeginPlot("##Main",ImVec2(-1,ImGui::GetTextLineHeight()*10))) { ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoTickLabels,ImPlotAxisFlags_NoTickLabels);