Skip to content

Commit

Permalink
Ability to query point, line and rect drags for their click, hovered …
Browse files Browse the repository at this point in the history
…and held states (#507)

* Added optional parameters to Drag.. functions to query state of items: clicked, hovered, held

See also #506

* Updated click/hover/held code

* Added demo
* DragRect is now report state of the entire rect

* Fix DragRect interaction; improve demo; some preferential changes

* Added held state handling for the DragRect rectangle

---------

Co-authored-by: epezent <[email protected]>
  • Loading branch information
pozemka and epezent authored Aug 23, 2023
1 parent 4f47cdf commit 7b91715
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 55 deletions.
111 changes: 72 additions & 39 deletions implot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3894,7 +3894,7 @@ IMPLOT_API void TagYV(double y, const ImVec4& color, const char* fmt, va_list ar

static const float DRAG_GRAB_HALF_SIZE = 4.0f;

bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags) {
bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
ImGui::PushID("#IMPLOT_DRAG_POINT");
IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragPoint() needs to be called between BeginPlot() and EndPlot()!");
SetupLock();
Expand All @@ -3916,30 +3916,34 @@ bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius,
bool hovered = false, held = false;

ImGui::KeepAliveID(id);
if (input)
ImGui::ButtonBehavior(rect,id,&hovered,&held);
if (input) {
bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held);
if (out_clicked) *out_clicked = clicked;
if (out_hovered) *out_hovered = hovered;
if (out_held) *out_held = held;
}

bool dragging = false;
bool modified = false;
if (held && ImGui::IsMouseDragging(0)) {
*x = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;
*y = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;
dragging = true;
modified = true;
}

PushPlotClipRect();
ImDrawList& DrawList = *GetPlotDrawList();
if ((hovered || held) && show_curs)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (dragging && no_delay)
if (modified && no_delay)
pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO);
DrawList.AddCircleFilled(pos, radius, col32);
PopPlotClipRect();

ImGui::PopID();
return dragging;
return modified;
}

bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags) {
bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
// ImGui::PushID("#IMPLOT_DRAG_LINE_X");
ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineX() needs to be called between BeginPlot() and EndPlot()!");
Expand All @@ -3961,8 +3965,12 @@ bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPl
bool hovered = false, held = false;

ImGui::KeepAliveID(id);
if (input)
ImGui::ButtonBehavior(rect,id,&hovered,&held);
if (input) {
bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held);
if (out_clicked) *out_clicked = clicked;
if (out_hovered) *out_hovered = hovered;
if (out_held) *out_held = held;
}

if ((hovered || held) && show_curs)
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
Expand All @@ -3971,26 +3979,26 @@ bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPl
ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;
ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);

bool dragging = false;
bool modified = false;
if (held && ImGui::IsMouseDragging(0)) {
*value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;
dragging = true;
modified = true;
}

PushPlotClipRect();
ImDrawList& DrawList = *GetPlotDrawList();
if (dragging && no_delay)
if (modified && no_delay)
x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x);
DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yb), col32, thickness);
DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yt+len), col32, 3*thickness);
DrawList.AddLine(ImVec2(x,yb), ImVec2(x,yb-len), col32, 3*thickness);
PopPlotClipRect();

// ImGui::PopID();
return dragging;
return modified;
}

bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags) {
bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
ImGui::PushID("#IMPLOT_DRAG_LINE_Y");
ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineY() needs to be called between BeginPlot() and EndPlot()!");
Expand All @@ -4013,8 +4021,12 @@ bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPl
bool hovered = false, held = false;

ImGui::KeepAliveID(id);
if (input)
ImGui::ButtonBehavior(rect,id,&hovered,&held);
if (input) {
bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held);
if (out_clicked) *out_clicked = clicked;
if (out_hovered) *out_hovered = hovered;
if (out_held) *out_held = held;
}

if ((hovered || held) && show_curs)
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
Expand All @@ -4023,26 +4035,26 @@ bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPl
ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;
ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);

bool dragging = false;
bool modified = false;
if (held && ImGui::IsMouseDragging(0)) {
*value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;
dragging = true;
modified = true;
}

PushPlotClipRect();
ImDrawList& DrawList = *GetPlotDrawList();
if (dragging && no_delay)
if (modified && no_delay)
y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y);
DrawList.AddLine(ImVec2(xl,y), ImVec2(xr,y), col32, thickness);
DrawList.AddLine(ImVec2(xl,y), ImVec2(xl+len,y), col32, 3*thickness);
DrawList.AddLine(ImVec2(xr,y), ImVec2(xr-len,y), col32, 3*thickness);
PopPlotClipRect();

ImGui::PopID();
return dragging;
return modified;
}

bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_max, const ImVec4& col, ImPlotDragToolFlags flags) {
bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_max, const ImVec4& col, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
ImGui::PushID("#IMPLOT_DRAG_RECT");
IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragRect() needs to be called between BeginPlot() and EndPlot()!");
SetupLock();
Expand Down Expand Up @@ -4079,13 +4091,18 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m
ImU32 col32_a = ImGui::ColorConvertFloat4ToU32(color);
const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);

bool dragging = false;
bool hovered = false, held = false;
bool modified = false;
bool clicked = false, hovered = false, held = false;
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);

ImGui::KeepAliveID(id);
if (input)
ImGui::ButtonBehavior(b_rect,id,&hovered,&held);
if (input) {
// middle point
clicked = ImGui::ButtonBehavior(b_rect,id,&hovered,&held);
if (out_clicked) *out_clicked = clicked;
if (out_hovered) *out_hovered = hovered;
if (out_held) *out_held = held;
}

if ((hovered || held) && show_curs)
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);
Expand All @@ -4095,23 +4112,27 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m
*y[i] = pp.y;
*x[i] = pp.x;
}
dragging = true;
modified = true;
}

for (int i = 0; i < 4; ++i) {
// points
b_rect = ImRect(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);
ImGuiID p_id = id + i + 1;
ImGui::KeepAliveID(p_id);
if (input)
ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held);
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;
dragging = true;
modified = true;
}

// edges
Expand All @@ -4121,16 +4142,20 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m
: 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);
ImGuiID e_id = id + i + 5;
ImGui::KeepAliveID(e_id);
if (input)
ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held);
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;
dragging = true;
modified = true;
}
if (hovered && ImGui::IsMouseDoubleClicked(0))
{
Expand All @@ -4139,33 +4164,41 @@ bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_m
*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;
dragging = true;
modified = true;
}
}

const bool mouse_inside = rect_grab.Contains(ImGui::GetMousePos());
const bool mouse_clicked = ImGui::IsMouseClicked(0);
const bool mouse_down = ImGui::IsMouseDown(0);
if (input && mouse_inside) {
if (out_clicked) *out_clicked = *out_clicked || mouse_clicked;
if (out_hovered) *out_hovered = true;
if (out_held) *out_held = *out_held || mouse_down;
}

PushPlotClipRect();
ImDrawList& DrawList = *GetPlotDrawList();
if (dragging && no_delay) {
if (modified && no_delay) {
for (int i = 0; i < 4; ++i)
p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO);
pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO);
rect = ImRect(ImMin(p[0],p[2]),ImMax(p[0],p[2]));
}
DrawList.AddRectFilled(rect.Min, rect.Max, col32_a);
DrawList.AddRect(rect.Min, rect.Max, col32);
if (input && (dragging || rect_grab.Contains(ImGui::GetMousePos()))) {
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);
}
PopPlotClipRect();
ImGui::PopID();
return dragging;
return modified;
}

bool DragRect(int id, ImPlotRect* bounds, const ImVec4& col, ImPlotDragToolFlags flags) {
return DragRect(id, &bounds->X.Min, &bounds->Y.Min,&bounds->X.Max, &bounds->Y.Max, col, flags);
bool DragRect(int id, ImPlotRect* bounds, const ImVec4& col, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) {
return DragRect(id, &bounds->X.Min, &bounds->Y.Min,&bounds->X.Max, &bounds->Y.Max, col, flags, out_clicked, out_hovered, out_held);
}

//-----------------------------------------------------------------------------
Expand Down
12 changes: 7 additions & 5 deletions implot.h
Original file line number Diff line number Diff line change
Expand Up @@ -924,16 +924,18 @@ IMPLOT_API void PlotDummy(const char* label_id, ImPlotDummyFlags flags=0);

// The following can be used to render interactive elements and/or annotations.
// Like the item plotting functions above, they apply to the current x and y
// axes, which can be changed with `SetAxis/SetAxes`.
// axes, which can be changed with `SetAxis/SetAxes`. These functions return true
// when user interaction causes the provided coordinates to change. Additional
// user interactions can be retrieved through the optional output parameters.

// Shows a draggable point at x,y. #col defaults to ImGuiCol_Text.
IMPLOT_API bool DragPoint(int id, double* x, double* y, const ImVec4& col, float size = 4, ImPlotDragToolFlags flags=0);
IMPLOT_API bool DragPoint(int id, double* x, double* y, const ImVec4& col, float size = 4, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr);
// Shows a draggable vertical guide line at an x-value. #col defaults to ImGuiCol_Text.
IMPLOT_API bool DragLineX(int id, double* x, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags=0);
IMPLOT_API bool DragLineX(int id, double* x, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr);
// Shows a draggable horizontal guide line at a y-value. #col defaults to ImGuiCol_Text.
IMPLOT_API bool DragLineY(int id, double* y, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags=0);
IMPLOT_API bool DragLineY(int id, double* y, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr);
// Shows a draggable and resizeable rectangle.
IMPLOT_API bool DragRect(int id, double* x1, double* y1, double* x2, double* y2, const ImVec4& col, ImPlotDragToolFlags flags=0);
IMPLOT_API bool DragRect(int id, double* x1, double* y1, double* x2, double* y2, const ImVec4& col, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr);

// Shows an annotation callout at a chosen point. Clamping keeps annotations in the plot area. Annotations are always rendered on top.
IMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, bool round = false);
Expand Down
Loading

0 comments on commit 7b91715

Please sign in to comment.