Demo: Improve "Custom Rendering"->"Canvas" demo with a grid, scrolling and context menu.
diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt
index ea67baa..70acf40 100644
--- a/docs/CHANGELOG.txt
+++ b/docs/CHANGELOG.txt
@@ -68,6 +68,8 @@
by modifying the 'style.CircleSegmentMaxError' value. [@ShironekoBen]
- ImDrawList: Fixed minor bug introduced in 1.75 where AddCircle() with 12 segments would generate
an extra vertex. (This bug was mistakenly marked as fixed in earlier 1.77 release). [@ShironekoBen]
+- Demo: Improve "Custom Rendering"->"Canvas" demo with a grid, scrolling and context menu.
+ Also showcase using InvisibleButton() will multiple mouse buttons flags.
- Demo: Tweak "Child Windows" section.
- Style Editor: Added preview of circle auto-tessellation when editing the corresponding value.
- Backends: OpenGL3: Added support for glad2 loader. (#3330) [@moritz-h]
diff --git a/imgui_demo.cpp b/imgui_demo.cpp
index 2acee76..0ce046c 100644
--- a/imgui_demo.cpp
+++ b/imgui_demo.cpp
@@ -4859,13 +4859,13 @@
// overloaded operators, etc. Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your
// types and ImVec2/ImVec4. Dear ImGui defines overloaded operators but they are internal to imgui.cpp and not
// exposed outside (to avoid messing with your types) In this example we are not using the maths operators!
- ImDrawList* draw_list = ImGui::GetWindowDrawList();
if (ImGui::BeginTabBar("##TabBar"))
{
if (ImGui::BeginTabItem("Primitives"))
{
ImGui::PushItemWidth(-ImGui::GetFontSize() * 10);
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
// Draw gradients
// (note that those are currently exacerbating our sRGB/Linear issues)
@@ -4949,56 +4949,95 @@
if (ImGui::BeginTabItem("Canvas"))
{
- static ImVector<ImVec2> points;
+ struct ItemLine { ImVec2 p0, p1; ItemLine(const ImVec2& _p0, const ImVec2& _p1) { p0 = _p0; p1 = _p1; } };
+ static ImVector<ItemLine> lines;
+ static ImVec2 scrolling(0.0f, 0.0f);
+ static bool show_grid = true;
static bool adding_line = false;
- if (ImGui::Button("Clear")) points.clear();
- if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } }
- ImGui::Text("Left-click and drag to add lines,\nRight-click to undo");
- // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use
- // IsItemHovered(). But you can also draw directly and poll mouse/keyboard by yourself.
- // You can manipulate the cursor using GetCursorPos() and SetCursorPos().
- // If you only use the ImDrawList API, you can notify the owner window of its extends with SetCursorPos(max).
- ImVec2 canvas_p = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates!
+ ImGui::Checkbox("Show grid", &show_grid);
+ ImGui::Text("Mouse Left: drag to add lines,\nMouse Right: drag to scroll, click for context menu.");
+
+ // Typically you would use a BeginChild()/EndChild() pair to benefit from a clipping region + own scrolling.
+ // Here we demonstrate that this can be replaced by simple offsetting + custom drawing + PushClipRect/PopClipRect() calls.
+ // To use a child window instead we could use, e.g:
+ // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Disable padding
+ // ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(50, 50, 50, 255)); // Set a background color
+ // ImGui::BeginChild("canvas", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_NoMove);
+ // ImGui::PopStyleColor();
+ // ImGui::PopStyleVar();
+ // [...]
+ // ImGui::EndChild();
+
+ // Using InvisibleButton() as a convenience 1) it will advance the layout cursor and 2) allows us to use IsItemHovered()/IsItemActive()
+ ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates!
ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available
if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f;
if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f;
- draw_list->AddRectFilledMultiColor(canvas_p, ImVec2(canvas_p.x + canvas_sz.x, canvas_p.y + canvas_sz.y), IM_COL32(50, 50, 50, 255), IM_COL32(50, 50, 60, 255), IM_COL32(60, 60, 70, 255), IM_COL32(50, 50, 60, 255));
- draw_list->AddRect(canvas_p, ImVec2(canvas_p.x + canvas_sz.x, canvas_p.y + canvas_sz.y), IM_COL32(255, 255, 255, 255));
+ ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);
- bool adding_preview = false;
- ImGui::InvisibleButton("canvas", canvas_sz);
- ImVec2 mouse_pos_global = ImGui::GetIO().MousePos;
- ImVec2 mouse_pos_canvas = ImVec2(mouse_pos_global.x - canvas_p.x, mouse_pos_global.y - canvas_p.y);
+ // Draw border and background color
+ ImGuiIO& io = ImGui::GetIO();
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+ draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255));
+ draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255));
+
+ // This will catch our interactions
+ ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
+ const bool is_hovered = ImGui::IsItemHovered(); // Hovered
+ const bool is_active = ImGui::IsItemActive(); // Held
+ const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); // Lock scrolled origin
+ const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
+
+ // Add first and second point
+ if (is_hovered && !adding_line && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
+ {
+ lines.push_back(ItemLine(mouse_pos_in_canvas, mouse_pos_in_canvas));
+ adding_line = true;
+ }
if (adding_line)
{
- adding_preview = true;
- points.push_back(mouse_pos_canvas);
- if (!ImGui::IsMouseDown(0))
- adding_line = adding_preview = false;
- }
- if (ImGui::IsItemHovered())
- {
- if (!adding_line && ImGui::IsMouseClicked(0))
- {
- points.push_back(mouse_pos_canvas);
- adding_line = true;
- }
- if (ImGui::IsMouseClicked(1) && !points.empty())
- {
- adding_line = adding_preview = false;
- points.pop_back();
- points.pop_back();
- }
+ lines.back().p1 = mouse_pos_in_canvas;
+ if (!ImGui::IsMouseDown(ImGuiMouseButton_Left))
+ adding_line = false;
}
- // Draw all lines in the canvas (with a clipping rectangle so they don't stray out of it).
- draw_list->PushClipRect(canvas_p, ImVec2(canvas_p.x + canvas_sz.x, canvas_p.y + canvas_sz.y), true);
- for (int i = 0; i < points.Size - 1; i += 2)
- draw_list->AddLine(ImVec2(canvas_p.x + points[i].x, canvas_p.y + points[i].y), ImVec2(canvas_p.x + points[i + 1].x, canvas_p.y + points[i + 1].y), IM_COL32(255, 255, 0, 255), 2.0f);
+ // Pan (using zero mouse threshold)
+ if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, 0.0f))
+ {
+ scrolling.x += io.MouseDelta.x;
+ scrolling.y += io.MouseDelta.y;
+ }
+
+ // Context menu (under default mouse threshold)
+ // We intentionally use the same button to demonstrate using mouse drag threshold. Some may feel panning should rely on same threshold.
+ ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
+ if (drag_delta.x == 0.0f && drag_delta.y == 0.0f)
+ ImGui::OpenPopupContextItem("context");
+ if (ImGui::BeginPopup("context"))
+ {
+ if (adding_line)
+ lines.pop_back();
+ adding_line = false;
+ if (ImGui::MenuItem("Remove one", NULL, false, lines.Size > 0)) { lines.pop_back(); }
+ if (ImGui::MenuItem("Remove all", NULL, false, lines.Size > 0)) { lines.clear(); }
+ ImGui::EndPopup();
+ }
+
+ // Draw grid + all lines in the canvas
+ draw_list->PushClipRect(canvas_p0, canvas_p1, true);
+ if (show_grid)
+ {
+ const float GRID_STEP = 64.0f;
+ for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP)
+ draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40));
+ for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP)
+ draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40));
+ }
+ for (int n = 0; n < lines.Size; n++)
+ draw_list->AddLine(ImVec2(origin.x + lines[n].p0.x, origin.y + lines[n].p0.y), ImVec2(origin.x + lines[n].p1.x, origin.y + lines[n].p1.y), IM_COL32(255, 255, 0, 255), 2.0f);
draw_list->PopClipRect();
- if (adding_preview)
- points.pop_back();
+
ImGui::EndTabItem();
}