From 44c7dfca0304ba5ab22caf351203f84a16d206d9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 27 Feb 2024 19:42:20 +0100 Subject: [PATCH 1/8] Menus, Popup: Amend c3f8f4d for static analyzer warning ("condition always true"). (#7325) --- imgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9648c797..6a14012c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10891,8 +10891,8 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ if (restore_focus_to_window_under_popup && prev_popup.Window) { ImGuiWindow* popup_window = prev_popup.Window; - ImGuiWindow* focus_window = (popup_window && popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : prev_popup.RestoreNavWindow; - if (focus_window && !focus_window->WasActive && popup_window) + ImGuiWindow* focus_window = (popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : prev_popup.RestoreNavWindow; + if (focus_window && !focus_window->WasActive) FocusTopMostWindowUnderOne(popup_window, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild); // Fallback else FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild : ImGuiFocusRequestFlags_None); From 0573513d6df54fc7cff93a640dc8bb05b05b949d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Feb 2024 17:09:20 +0100 Subject: [PATCH 2/8] Windows: Scrollbar visibility decision uses current size when both size and contents size are submitted by API. (#7252) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 6 ++++++ imgui.h | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 48ccc0cf..3468c0a1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,8 @@ HOW TO UPDATE? Other changes: +- Windows: Scrollbar visibility decision uses current size when both size and contents + size are submitted by API. (#7252) - Menus, Popups: Fixed an issue where sibling menu popups re-opening in successive frames would erroneously close the window. While it is technically a popup issue it would generally manifest when fast moving the mouse bottom to top in a sub-menu. diff --git a/imgui.cpp b/imgui.cpp index 6a14012c..45d551ab 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6610,8 +6610,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; + // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible. + // Those flags will be altered further down in the function depending on more conditions. bool use_current_size_for_scrollbar_x = window_just_created; bool use_current_size_for_scrollbar_y = window_just_created; + if (window_size_x_set_by_api && window->ContentSizeExplicit.x != 0.0f) + use_current_size_for_scrollbar_x = true; + if (window_size_y_set_by_api && window->ContentSizeExplicit.y != 0.0f) // #7252 + use_current_size_for_scrollbar_y = true; // Collapse window by double-clicking on title bar // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing diff --git a/imgui.h b/imgui.h index 307834ff..cd707b40 100644 --- a/imgui.h +++ b/imgui.h @@ -24,7 +24,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.90.5 WIP" -#define IMGUI_VERSION_NUM 19042 +#define IMGUI_VERSION_NUM 19043 #define IMGUI_HAS_TABLE /* From c6236699671b2e5f94481c051cd19afe00cda583 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 28 Feb 2024 19:21:13 +0100 Subject: [PATCH 3/8] Added link to crawlable wiki --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index a2be7fde..6aac61b5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -162,6 +162,8 @@ See: [Upcoming Changes](https://github.com/ocornut/imgui/wiki/Upcoming-Changes). See: [Dear ImGui Test Engine + Test Suite](https://github.com/ocornut/imgui_test_engine) for Automation & Testing. +For the purposes of getting search engines to crawl the wiki, here's a link to the [Crawable Wiki](https://github-wiki-see.page/m/ocornut/imgui/wiki) (not for humans, [here's why](https://github-wiki-see.page/)). + Getting started? For first-time users having issues compiling/linking/running or issues loading fonts, please use [GitHub Discussions](https://github.com/ocornut/imgui/discussions). For ANY other questions, bug reports, requests, feedback, please post on [GitHub Issues](https://github.com/ocornut/imgui/issues). Please read and fill the New Issue template carefully. Private support is available for paying business customers (E-mail: _contact @ dearimgui dot com_). From 04f40014a62898d325cbc987c9f56073b2d17e73 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Feb 2024 15:17:08 +0100 Subject: [PATCH 4/8] Docs: added a mini wiki index in main source files. --- docs/CHANGELOG.txt | 6 ++++++ imgui.cpp | 22 +++++++++++++--------- imgui.h | 22 +++++++++++++--------- imgui_demo.cpp | 2 +- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3468c0a1..291d1f10 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,12 @@ Other changes: frames would erroneously close the window. While it is technically a popup issue it would generally manifest when fast moving the mouse bottom to top in a sub-menu. (#7325, #7287, #7063) +- Docs: added more wiki links to headers of imgui.h/imgui.cpp to facilitate discovery + of interesting resources, because github doesn't allow Wiki to be crawled by search engines. + - This is the main wiki: https://github.com/ocornut/imgui/wiki + - This is the crawlable version: https://github-wiki-see.page/m/ocornut/imgui/wiki + Adding a link to the crawlable version, even though it is not indended for humans, + to increase its search rank. ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index 45d551ab..31c6d251 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7,15 +7,19 @@ // - Read top of imgui.cpp for more details, links and comments. // Resources: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Homepage https://github.com/ocornut/imgui -// - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/6897 (please post your screenshots/video there!) -// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) -// - Glossary https://github.com/ocornut/imgui/wiki/Glossary -// - Issues & support https://github.com/ocornut/imgui/issues -// - Tests & Automation https://github.com/ocornut/imgui_test_engine +// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) +// - Homepage ................... https://github.com/ocornut/imgui +// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Gallery .................... https://github.com/ocornut/imgui/issues/6897 (please post your screenshots/video there!) +// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) +// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui +// - Issues & support ........... https://github.com/ocornut/imgui/issues +// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. diff --git a/imgui.h b/imgui.h index cd707b40..a6a3ec8b 100644 --- a/imgui.h +++ b/imgui.h @@ -7,15 +7,19 @@ // - Read top of imgui.cpp for more details, links and comments. // Resources: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Homepage https://github.com/ocornut/imgui -// - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/6897 (please post your screenshots/video there!) -// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) -// - Glossary https://github.com/ocornut/imgui/wiki/Glossary -// - Issues & support https://github.com/ocornut/imgui/issues -// - Tests & Automation https://github.com/ocornut/imgui_test_engine +// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) +// - Homepage ................... https://github.com/ocornut/imgui +// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Gallery .................... https://github.com/ocornut/imgui/issues/6897 (please post your screenshots/video there!) +// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) +// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui +// - Issues & support ........... https://github.com/ocornut/imgui/issues +// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d60afd04..f275abc4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -7,7 +7,7 @@ // - Need help integrating Dear ImGui in your codebase? // - Read Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started // - Read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. -// Read imgui.cpp for more details, documentation and comments. +// Read top of imgui.cpp and imgui.h for many details, documentation, comments, links. // Get the latest version at https://github.com/ocornut/imgui //--------------------------------------------------- From 1ff90c52d5f0d57566ba6054393817a1f8a81fe4 Mon Sep 17 00:00:00 2001 From: thedmd Date: Wed, 3 Jan 2024 14:31:45 +0100 Subject: [PATCH 5/8] ImDrawList: add PathFillConcave(), AddConcavePolyFilled() (#760) Extracted from 2023/12/29 post. WIP add PathFillConcave(), AddConcavePolyFilled() * remove use of 'auto' * IsConvex -> ImPathIsConvex * Triangulator -> ImTriangulator * ImTriangulator: split declaration from definition, ImTriangulator can be put in the header if necessary * ImTriangulator: Add node list flip to reverse winding order and handle degenerate cases * ImTriangulator: Remove _HeapStorage, always require scratch buffer to be provided * ImTriangulator: Use ImTriangleContainsPoint * AddConcavePolyFilled: Clone AddConvexPolyFilled and use triangulator * AddConcavePolyFilled: Remove ImDrawListEx_AddPolyFilled_xxx * AddConcavePolyFilled: Use _Data->TempBuffer in triangulator * AddConcavePolyFilled: --- imgui.h | 8 +- imgui_draw.cpp | 434 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index a6a3ec8b..4d5a8f01 100644 --- a/imgui.h +++ b/imgui.h @@ -2756,11 +2756,14 @@ struct ImDrawList IMGUI_API void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); - IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) + // General polygon + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); + IMGUI_API void AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col); + // Image primitives // - Read FAQ to understand what ImTextureID is. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. @@ -2776,6 +2779,7 @@ struct ImDrawList inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } + inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 218498f1..1bb7cfed 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -8,6 +8,7 @@ Index of this file: // [SECTION] STB libraries implementation // [SECTION] Style functions // [SECTION] ImDrawList +// [SECTION] ImDrawList concave polygon fill // [SECTION] ImDrawListSplitter // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions @@ -1700,6 +1701,439 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi PopTextureID(); } +//----------------------------------------------------------------------------- +// [SECTION] ImDrawList concave polygon fill +//----------------------------------------------------------------------------- + +static bool ImPathIsConvex(const ImVec2& a, const ImVec2& b, const ImVec2& c) +{ + const float dx0 = b.x - a.x; + const float dy0 = b.y - a.y; + + const float dx1 = c.x - b.x; + const float dy1 = c.y - b.y; + + return dx0 * dy1 - dx1 * dy0 > 0.0f; +} + +struct ImTriangulator +{ + struct Triangle { int Index[3]; }; + + static int EstimateTriangleCount(int points_count); + static int EstimateScratchBufferSize(int points_count); + + ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer); + ImTriangulator(const ImVec2* points, int points_count, int points_stride_bytes, void* scratch_buffer); + + bool HasNext() const; + + Triangle Next(); + +private: + enum Type { Convex, Ear, Reflex }; + + struct Node; + struct alignas(void*) Span + { + Node** Data = nullptr; + int Size = 0; + + void PushBack(Node* node); + void RemoveByIndex(int index); + }; + + void BuildNodes(); + void BuildReflexes(); + void BuildEars(); + void FlipNodeList(); + bool IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; + Type ClassifyNode(const Node* node) const; + void ReclasifyNode(Node* node); + + const ImVec2* _Points = nullptr; + int _PointsCount = 0; + int _PointsStrideBytes = 0; + int _TrianglesLeft = 0; + + Node* _Nodes = nullptr; + Span _Ears; + Span _Reflexes; +}; + +struct alignas(void*) ImTriangulator::Node +{ + Type Type = Convex; + int Index = 0; + const ImVec2* Point = nullptr; + Node* Next = nullptr; + Node* Prev = nullptr; + + void Unlink() + { + Next->Prev = Prev; + Prev->Next = Next; + } +}; + +int ImTriangulator::EstimateTriangleCount(int points_count) +{ + if (points_count < 3) + return 0; + + return points_count - 2; +} + +int ImTriangulator::EstimateScratchBufferSize(int points_count) +{ + return sizeof(Node*) * points_count * 2 + sizeof(Node) * points_count; +} + +ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer) + : ImTriangulator(points, points_count, sizeof(ImVec2), scratch_buffer) +{ +} + +ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, int points_stride_bytes, void* scratch_buffer) + : _Points(points) + , _PointsCount(points_count) + , _PointsStrideBytes(points_stride_bytes) + , _TrianglesLeft(EstimateTriangleCount(points_count)) +{ + IM_ASSERT(scratch_buffer != nullptr && "Must provide scratch buffer."); + IM_ASSERT(points_count >= 3); + + // Disable triangulator if scratch buffer isn't provided. + if (scratch_buffer == nullptr) + { + _TrianglesLeft = 0; + points_count = 0; + return; + } + + // Distribute storage for nodes, ears and reflexes. + _Nodes = reinterpret_cast(scratch_buffer); + _Ears.Data = reinterpret_cast(_Nodes + points_count); + _Reflexes.Data = _Ears.Data + points_count; + + BuildNodes(); + BuildReflexes(); + BuildEars(); +} + +void ImTriangulator::BuildNodes() +{ +# define IM_POINT_PTR(idx) reinterpret_cast(reinterpret_cast(_Points) + (idx) * _PointsStrideBytes) + + for (int i = 0; i < _PointsCount; ++i) + { + _Nodes[i].Type = Convex; + _Nodes[i].Index = static_cast(i); + _Nodes[i].Point = IM_POINT_PTR(i); + _Nodes[i].Next = _Nodes + i + 1; + _Nodes[i].Prev = _Nodes + i - 1; + } + _Nodes[0].Prev = _Nodes + _PointsCount - 1; + _Nodes[_PointsCount - 1].Next = _Nodes; + +# undef IM_POINT_PTR +} + +void ImTriangulator::BuildReflexes() +{ + Node* node = _Nodes; + for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) + { + const ImVec2& v0 = *node->Prev->Point; + const ImVec2& v1 = *node->Point; + const ImVec2& v2 = *node->Next->Point; + + if (ImPathIsConvex(v0, v1, v2)) + continue; + + node->Type = Reflex; + _Reflexes.PushBack(node); + } +} + +void ImTriangulator::BuildEars() +{ + Node* node = _Nodes; + for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) + { + if (node->Type != Convex) + continue; + + const int i0 = node->Prev->Index; + const int i1 = node->Index; + const int i2 = node->Next->Index; + + const ImVec2& v0 = *node->Prev->Point; + const ImVec2& v1 = *node->Point; + const ImVec2& v2 = *node->Next->Point; + + if (!IsEar(node, i0, i1, i2, v0, v1, v2)) + continue; + + node->Type = Ear; + _Ears.PushBack(node); + } +} + +bool ImTriangulator::HasNext() const +{ + return _TrianglesLeft > 0; +} + +ImTriangulator::Triangle ImTriangulator::Next() +{ + IM_ASSERT(_TrianglesLeft > 0 && "Do not call Next() until HasNext() return true"); + + if (_Ears.Size == 0) + { + FlipNodeList(); + + Node* node = _Nodes; + for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) + node->Type = Convex; + _Reflexes.Size = 0; + BuildReflexes(); + BuildEars(); + + // If we still don't have ears, it means geometry is degenerated. + if (_Ears.Size == 0) + { + IM_ASSERT(_TrianglesLeft > 0 && "Geometry is degenerated"); + + // Return first triangle available, mimicking the behavior of convex fill. + _Ears.Data[0] = _Nodes; + _Ears.Size = 1; + } + } + + Node* ear = _Ears.Data[--_Ears.Size]; + + const int i0 = ear->Prev->Index; + const int i1 = ear->Index; + const int i2 = ear->Next->Index; + + ear->Unlink(); + if (ear == _Nodes) + _Nodes = ear->Next; + + ReclasifyNode(ear->Prev); + ReclasifyNode(ear->Next); + + --_TrianglesLeft; + + return Triangle{ { i0, i1, i2 } }; +} + +void ImTriangulator::Span::PushBack(Node* node) +{ + Data[Size++] = node; +} + +void ImTriangulator::Span::RemoveByIndex(int index) +{ + for (int i = Size - 1; i >= 0; --i) + { + if (Data[i]->Index == index) + { + Data[i] = Data[Size - 1]; + --Size; + break; + } + } +} + +void ImTriangulator::FlipNodeList() +{ + Node* prev = _Nodes; + Node* temp = _Nodes; + Node* current = _Nodes->Next; + + prev->Next = prev; + prev->Prev = prev; + + while (current != _Nodes) + { + temp = current->Next; + + current->Next = prev; + prev->Prev = current; + _Nodes->Next = current; + current->Prev = _Nodes; + + prev = current; + current = temp; + } + + _Nodes = prev; +} + +bool ImTriangulator::IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const +{ + for (int i = 0; i < _Reflexes.Size; ++i) + { + Node* reflex = _Reflexes.Data[i]; + + if (reflex->Index == i0 || reflex->Index == i1 || reflex->Index == i2) + continue; + + if (ImTriangleContainsPoint(v0, v1, v2, *reflex->Point)) + return false; + } + + return true; +} + +ImTriangulator::Type ImTriangulator::ClassifyNode(const Node* node) const +{ + const int i0 = node->Prev->Index; + const int i1 = node->Index; + const int i2 = node->Next->Index; + + const ImVec2& v0 = *node->Prev->Point; + const ImVec2& v1 = *node->Point; + const ImVec2& v2 = *node->Next->Point; + + if (ImPathIsConvex(v0, v1, v2)) + { + if (IsEar(node, i0, i1, i2, v0, v1, v2)) + return Ear; + else + return Convex; + } + else + { + return Reflex; + } +} + +void ImTriangulator::ReclasifyNode(Node* node) +{ + Type type = ClassifyNode(node); + + if (type == node->Type) + return; + + if (node->Type == Reflex) + _Reflexes.RemoveByIndex(node->Index); + else if (node->Type == Ear) + _Ears.RemoveByIndex(node->Index); + + if (type == Reflex) + _Reflexes.PushBack(node); + else if (type == Ear) + _Ears.PushBack(node); + + node->Type = type; +} + +void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) + return; + + // coarse culling against viewport to avoid processing triangles outside of the visible area + ImVec2 bounds_min = ImVec2(FLT_MAX, FLT_MAX); + ImVec2 bounds_max = ImVec2(-FLT_MAX, -FLT_MAX); + + for (int i = 0; i < points_count; ++i) + { + const ImVec2& pos = points[i]; + + bounds_min = ImMin(bounds_min, pos); + bounds_max = ImMax(bounds_max, pos); + } + + if (!ImRect(_ClipRectStack.back()).Overlaps(ImRect(bounds_min, bounds_max))) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = _FringeScale; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count - 2)*3 + points_count * 6; + const int vtx_count = (points_count * 2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; + + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); + ImTriangulator triangulator = ImTriangulator(points, points_count, _Data->TempBuffer.Data); + while (triangulator.HasNext()) + { + ImTriangulator::Triangle triangle = triangulator.Next(); + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[2] << 1)); + _IdxWritePtr += 3; + } + + // Compute normals + _Data->TempBuffer.reserve_discard(points_count); + ImVec2* temp_normals = _Data->TempBuffer.Data; + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + temp_normals[i0].x = dy; + temp_normals[i0].y = -dx; + } + + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + float dm_x = (n0.x + n1.x) * 0.5f; + float dm_y = (n0.y + n1.y) * 0.5f; + IM_FIXNORMAL2F(dm_x, dm_y); + dm_x *= AA_SIZE * 0.5f; + dm_y *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count - 2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); + ImTriangulator triangulator = ImTriangulator(points, points_count, _Data->TempBuffer.Data); + while (triangulator.HasNext()) + { + ImTriangulator::Triangle triangle = triangulator.Next(); + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[2]); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} //----------------------------------------------------------------------------- // [SECTION] ImDrawListSplitter From fbf45ad149b10ff8d9cb97aefe0dc5a9562fd66e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 9 Jan 2024 23:36:26 +0100 Subject: [PATCH 6/8] ImDrawList: add PathFillConcave(), AddConcavePolyFilled(): amends (#760) - Simplify and compact some code. Shallow tweaks. - Add comments. - Add concave shape demo. - Remove coarse culling. - Remove nested types to match coding style and for consistent type nams when translated to other languages. - Merged ClassifyNode() and ReclassifyNode(). - Extracted ImTriangleIsClockwise(). - Hold copy of points inside nodes instead of pointing to them. --- docs/CHANGELOG.txt | 2 + imgui.h | 6 +- imgui_demo.cpp | 20 ++- imgui_draw.cpp | 405 ++++++++++++++++----------------------------- imgui_internal.h | 3 +- 5 files changed, 162 insertions(+), 274 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 291d1f10..0cb8982e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,8 @@ Other changes: frames would erroneously close the window. While it is technically a popup issue it would generally manifest when fast moving the mouse bottom to top in a sub-menu. (#7325, #7287, #7063) +- DrawList: Added AddConcavePolyFilled(), PathFillConcave() concave filling. (#760) [@thedmd] + Note that only simple polygons (no self-intersections, no holes) are supported. - Docs: added more wiki links to headers of imgui.h/imgui.cpp to facilitate discovery of interesting resources, because github doesn't allow Wiki to be crawled by search engines. - This is the main wiki: https://github.com/ocornut/imgui/wiki diff --git a/imgui.h b/imgui.h index 4d5a8f01..598f565a 100644 --- a/imgui.h +++ b/imgui.h @@ -28,7 +28,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.90.5 WIP" -#define IMGUI_VERSION_NUM 19043 +#define IMGUI_VERSION_NUM 19044 #define IMGUI_HAS_TABLE /* @@ -2760,9 +2760,11 @@ struct ImDrawList IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) // General polygon + // - Only simple polygons are supported by filling functions (no self-intersections, no holes). + // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience fo user but not used by main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); - IMGUI_API void AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col); + IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives // - Read FAQ to understand what ImTextureID is. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f275abc4..63560640 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -7964,6 +7964,14 @@ static void ShowExampleAppWindowTitles(bool*) // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() //----------------------------------------------------------------------------- +// Add a |_| looking shape +static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) +{ + const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; + for (const ImVec2& p : pos_norms) + draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y))); +} + // Demonstrate using the low-level ImDrawList to draw custom shapes. static void ShowExampleAppCustomRendering(bool* p_open) { @@ -8053,6 +8061,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th); x += sz + spacing; // Concave Shape + //draw_list->AddPolyline(concave_shape, IM_ARRAYSIZE(concave_shape), col, ImDrawFlags_Closed, th); draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line @@ -8082,6 +8092,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) @@ -8097,15 +8108,10 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->PathFillConvex(col); x += sz + spacing; - // Cubic Bezier Curve (4 control points): this is concave so not drawing it yet - //draw_list->PathLineTo(ImVec2(x + cp4[0].x, y + cp4[0].y)); - //draw_list->PathBezierCubicCurveTo(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), curve_segments); - //draw_list->PathFillConvex(col); - //x += sz + spacing; - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); + x += sz + spacing; - ImGui::Dummy(ImVec2((sz + spacing) * 12.2f, (sz + spacing) * 3.0f)); + ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); ImGui::PopItemWidth(); ImGui::EndTabItem(); } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1bb7cfed..ad3fda90 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -8,7 +8,7 @@ Index of this file: // [SECTION] STB libraries implementation // [SECTION] Style functions // [SECTION] ImDrawList -// [SECTION] ImDrawList concave polygon fill +// [SECTION] ImTriangulator, ImDrawList concave polygon fill // [SECTION] ImDrawListSplitter // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions @@ -1702,200 +1702,130 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi } //----------------------------------------------------------------------------- -// [SECTION] ImDrawList concave polygon fill +// [SECTION] ImTriangulator, ImDrawList concave polygon fill +//----------------------------------------------------------------------------- +// Triangulate concave polygons. Based on "Triangulation by Ear Clipping" paper, O(N^2) complexity. +// Reference: https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf +// Provided as a convenience for user but not used by main library. +//----------------------------------------------------------------------------- +// - ImTriangulator [Internal] +// - AddConcavePolyFilled() //----------------------------------------------------------------------------- -static bool ImPathIsConvex(const ImVec2& a, const ImVec2& b, const ImVec2& c) -{ - const float dx0 = b.x - a.x; - const float dy0 = b.y - a.y; - - const float dx1 = c.x - b.x; - const float dy1 = c.y - b.y; - - return dx0 * dy1 - dx1 * dy0 > 0.0f; -} - -struct ImTriangulator +enum ImTriangulatorNodeType { - struct Triangle { int Index[3]; }; - - static int EstimateTriangleCount(int points_count); - static int EstimateScratchBufferSize(int points_count); - - ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer); - ImTriangulator(const ImVec2* points, int points_count, int points_stride_bytes, void* scratch_buffer); - - bool HasNext() const; - - Triangle Next(); - -private: - enum Type { Convex, Ear, Reflex }; - - struct Node; - struct alignas(void*) Span - { - Node** Data = nullptr; - int Size = 0; - - void PushBack(Node* node); - void RemoveByIndex(int index); - }; - - void BuildNodes(); - void BuildReflexes(); - void BuildEars(); - void FlipNodeList(); - bool IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; - Type ClassifyNode(const Node* node) const; - void ReclasifyNode(Node* node); - - const ImVec2* _Points = nullptr; - int _PointsCount = 0; - int _PointsStrideBytes = 0; - int _TrianglesLeft = 0; - - Node* _Nodes = nullptr; - Span _Ears; - Span _Reflexes; + ImTriangulatorNodeType_Convex, + ImTriangulatorNodeType_Ear, + ImTriangulatorNodeType_Reflex }; -struct alignas(void*) ImTriangulator::Node +struct ImTriangulatorNode { - Type Type = Convex; - int Index = 0; - const ImVec2* Point = nullptr; - Node* Next = nullptr; - Node* Prev = nullptr; + ImTriangulatorNodeType Type; + int Index; + ImVec2 Pos; + ImTriangulatorNode* Next; + ImTriangulatorNode* Prev; - void Unlink() - { - Next->Prev = Prev; - Prev->Next = Next; - } + void Unlink() { Next->Prev = Prev; Prev->Next = Next; } }; -int ImTriangulator::EstimateTriangleCount(int points_count) -{ - if (points_count < 3) - return 0; - - return points_count - 2; -} - -int ImTriangulator::EstimateScratchBufferSize(int points_count) +struct ImTriangulatorNodeSpan { - return sizeof(Node*) * points_count * 2 + sizeof(Node) * points_count; -} + ImTriangulatorNode** Data = NULL; + int Size = 0; -ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer) - : ImTriangulator(points, points_count, sizeof(ImVec2), scratch_buffer) -{ -} + void push_back(ImTriangulatorNode* node) { Data[Size++] = node; } + void find_erase_unsorted(int idx) { for (int i = Size - 1; i >= 0; i--) if (Data[i]->Index == idx) { Data[i] = Data[Size - 1]; Size--; return; } } +}; -ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, int points_stride_bytes, void* scratch_buffer) - : _Points(points) - , _PointsCount(points_count) - , _PointsStrideBytes(points_stride_bytes) - , _TrianglesLeft(EstimateTriangleCount(points_count)) +struct ImTriangulator { - IM_ASSERT(scratch_buffer != nullptr && "Must provide scratch buffer."); - IM_ASSERT(points_count >= 3); - - // Disable triangulator if scratch buffer isn't provided. - if (scratch_buffer == nullptr) - { - _TrianglesLeft = 0; - points_count = 0; - return; - } - - // Distribute storage for nodes, ears and reflexes. - _Nodes = reinterpret_cast(scratch_buffer); - _Ears.Data = reinterpret_cast(_Nodes + points_count); - _Reflexes.Data = _Ears.Data + points_count; + static int EstimateTriangleCount(int points_count) { return (points_count < 3) ? 0 : points_count - 2; } + static int EstimateScratchBufferSize(int points_count) { return sizeof(ImTriangulatorNode) * points_count + sizeof(ImTriangulatorNode*) * points_count * 2; } + + void Init(const ImVec2* points, int points_count, void* scratch_buffer); + void GetNextTriangle(unsigned int out_triangle[3]); // Return relative indexes for next triangle + + // Internal functions + void BuildNodes(const ImVec2* points, int points_count); + void BuildReflexes(); + void BuildEars(); + void FlipNodeList(); + bool IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; + void ReclassifyNode(ImTriangulatorNode* node); + + // Internal members + int _TrianglesLeft = 0; + ImTriangulatorNode* _Nodes = NULL; + ImTriangulatorNodeSpan _Ears; + ImTriangulatorNodeSpan _Reflexes; +}; - BuildNodes(); +// Distribute storage for nodes, ears and reflexes. +// FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer +// (this would require first building reflexes to bail to convex if empty, without even building nodes) +void ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_buffer) +{ + IM_ASSERT(scratch_buffer != NULL && points_count >= 3); + _TrianglesLeft = EstimateTriangleCount(points_count); + _Nodes = (ImTriangulatorNode*)scratch_buffer; // points_count x Node + _Ears.Data = (ImTriangulatorNode**)(_Nodes + points_count); // points_count x Node* + _Reflexes.Data = (ImTriangulatorNode**)(_Nodes + points_count) + points_count; // points_count x Node* + BuildNodes(points, points_count); BuildReflexes(); BuildEars(); } -void ImTriangulator::BuildNodes() +void ImTriangulator::BuildNodes(const ImVec2* points, int points_count) { -# define IM_POINT_PTR(idx) reinterpret_cast(reinterpret_cast(_Points) + (idx) * _PointsStrideBytes) - - for (int i = 0; i < _PointsCount; ++i) + for (int i = 0; i < points_count; i++) { - _Nodes[i].Type = Convex; - _Nodes[i].Index = static_cast(i); - _Nodes[i].Point = IM_POINT_PTR(i); - _Nodes[i].Next = _Nodes + i + 1; - _Nodes[i].Prev = _Nodes + i - 1; + _Nodes[i].Type = ImTriangulatorNodeType_Convex; + _Nodes[i].Index = i; + _Nodes[i].Pos = points[i]; + _Nodes[i].Next = _Nodes + i + 1; + _Nodes[i].Prev = _Nodes + i - 1; } - _Nodes[0].Prev = _Nodes + _PointsCount - 1; - _Nodes[_PointsCount - 1].Next = _Nodes; - -# undef IM_POINT_PTR + _Nodes[0].Prev = _Nodes + points_count - 1; + _Nodes[points_count - 1].Next = _Nodes; } void ImTriangulator::BuildReflexes() { - Node* node = _Nodes; - for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) + ImTriangulatorNode* n1 = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next) { - const ImVec2& v0 = *node->Prev->Point; - const ImVec2& v1 = *node->Point; - const ImVec2& v2 = *node->Next->Point; - - if (ImPathIsConvex(v0, v1, v2)) + if (ImTriangleIsClockwise(n1->Prev->Pos, n1->Pos, n1->Next->Pos)) continue; - - node->Type = Reflex; - _Reflexes.PushBack(node); + n1->Type = ImTriangulatorNodeType_Reflex; + _Reflexes.push_back(n1); } } void ImTriangulator::BuildEars() { - Node* node = _Nodes; - for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) + ImTriangulatorNode* n1 = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next) { - if (node->Type != Convex) + if (n1->Type != ImTriangulatorNodeType_Convex) continue; - - const int i0 = node->Prev->Index; - const int i1 = node->Index; - const int i2 = node->Next->Index; - - const ImVec2& v0 = *node->Prev->Point; - const ImVec2& v1 = *node->Point; - const ImVec2& v2 = *node->Next->Point; - - if (!IsEar(node, i0, i1, i2, v0, v1, v2)) + if (!IsEar(n1->Prev->Index, n1->Index, n1->Next->Index, n1->Prev->Pos, n1->Pos, n1->Next->Pos)) continue; - - node->Type = Ear; - _Ears.PushBack(node); + n1->Type = ImTriangulatorNodeType_Ear; + _Ears.push_back(n1); } } -bool ImTriangulator::HasNext() const -{ - return _TrianglesLeft > 0; -} - -ImTriangulator::Triangle ImTriangulator::Next() +void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3]) { - IM_ASSERT(_TrianglesLeft > 0 && "Do not call Next() until HasNext() return true"); - if (_Ears.Size == 0) { FlipNodeList(); - Node* node = _Nodes; - for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) - node->Type = Convex; + ImTriangulatorNode* node = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next) + node->Type = ImTriangulatorNodeType_Convex; _Reflexes.Size = 0; BuildReflexes(); BuildEars(); @@ -1903,59 +1833,34 @@ ImTriangulator::Triangle ImTriangulator::Next() // If we still don't have ears, it means geometry is degenerated. if (_Ears.Size == 0) { - IM_ASSERT(_TrianglesLeft > 0 && "Geometry is degenerated"); - // Return first triangle available, mimicking the behavior of convex fill. + IM_ASSERT(_TrianglesLeft > 0); // Geometry is degenerated _Ears.Data[0] = _Nodes; _Ears.Size = 1; } } - Node* ear = _Ears.Data[--_Ears.Size]; - - const int i0 = ear->Prev->Index; - const int i1 = ear->Index; - const int i2 = ear->Next->Index; + ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size]; + out_triangle[0] = ear->Prev->Index; + out_triangle[1] = ear->Index; + out_triangle[2] = ear->Next->Index; ear->Unlink(); if (ear == _Nodes) _Nodes = ear->Next; - ReclasifyNode(ear->Prev); - ReclasifyNode(ear->Next); - - --_TrianglesLeft; - - return Triangle{ { i0, i1, i2 } }; -} - -void ImTriangulator::Span::PushBack(Node* node) -{ - Data[Size++] = node; -} - -void ImTriangulator::Span::RemoveByIndex(int index) -{ - for (int i = Size - 1; i >= 0; --i) - { - if (Data[i]->Index == index) - { - Data[i] = Data[Size - 1]; - --Size; - break; - } - } + ReclassifyNode(ear->Prev); + ReclassifyNode(ear->Next); + _TrianglesLeft--; } void ImTriangulator::FlipNodeList() { - Node* prev = _Nodes; - Node* temp = _Nodes; - Node* current = _Nodes->Next; - + ImTriangulatorNode* prev = _Nodes; + ImTriangulatorNode* temp = _Nodes; + ImTriangulatorNode* current = _Nodes->Next; prev->Next = prev; prev->Prev = prev; - while (current != _Nodes) { temp = current->Next; @@ -1968,97 +1873,69 @@ void ImTriangulator::FlipNodeList() prev = current; current = temp; } - _Nodes = prev; } -bool ImTriangulator::IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const +// A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) +bool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const { - for (int i = 0; i < _Reflexes.Size; ++i) + ImTriangulatorNode** p_end = _Reflexes.Data + _Reflexes.Size; + for (ImTriangulatorNode** p = _Reflexes.Data; p < p_end; p++) { - Node* reflex = _Reflexes.Data[i]; - - if (reflex->Index == i0 || reflex->Index == i1 || reflex->Index == i2) - continue; - - if (ImTriangleContainsPoint(v0, v1, v2, *reflex->Point)) - return false; + ImTriangulatorNode* reflex = *p; + if (reflex->Index != i0 && reflex->Index != i1 && reflex->Index != i2) + if (ImTriangleContainsPoint(v0, v1, v2, reflex->Pos)) + return false; } - return true; } -ImTriangulator::Type ImTriangulator::ClassifyNode(const Node* node) const +void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1) { - const int i0 = node->Prev->Index; - const int i1 = node->Index; - const int i2 = node->Next->Index; - - const ImVec2& v0 = *node->Prev->Point; - const ImVec2& v1 = *node->Point; - const ImVec2& v2 = *node->Next->Point; - - if (ImPathIsConvex(v0, v1, v2)) - { - if (IsEar(node, i0, i1, i2, v0, v1, v2)) - return Ear; - else - return Convex; - } + // Classify node + ImTriangulatorNodeType type; + const ImTriangulatorNode* n0 = n1->Prev; + const ImTriangulatorNode* n2 = n1->Next; + if (!ImTriangleIsClockwise(n0->Pos, n1->Pos, n2->Pos)) + type = ImTriangulatorNodeType_Reflex; + else if (IsEar(n0->Index, n1->Index, n2->Index, n0->Pos, n1->Pos, n2->Pos)) + type = ImTriangulatorNodeType_Ear; else - { - return Reflex; - } -} - -void ImTriangulator::ReclasifyNode(Node* node) -{ - Type type = ClassifyNode(node); + type = ImTriangulatorNodeType_Convex; - if (type == node->Type) + // Update lists when a type changes + if (type == n1->Type) return; - - if (node->Type == Reflex) - _Reflexes.RemoveByIndex(node->Index); - else if (node->Type == Ear) - _Ears.RemoveByIndex(node->Index); - - if (type == Reflex) - _Reflexes.PushBack(node); - else if (type == Ear) - _Ears.PushBack(node); - - node->Type = type; -} - + if (n1->Type == ImTriangulatorNodeType_Reflex) + _Reflexes.find_erase_unsorted(n1->Index); + else if (n1->Type == ImTriangulatorNodeType_Ear) + _Ears.find_erase_unsorted(n1->Index); + if (type == ImTriangulatorNodeType_Reflex) + _Reflexes.push_back(n1); + else if (type == ImTriangulatorNodeType_Ear) + _Ears.push_back(n1); + n1->Type = type; +} + +// Use ear-clipping algorithm to triangulate a simple polygon (no self-interaction, no holes). +// (Reminder: we don't perform any coarse clipping/culling in ImDrawList layer! +// It is up to caller to ensure not making costly calls that will be outside of visible area. +// As concave fill is noticeably more expensive than other primitives, be mindful of this... +// Caller can build AABB of points, and avoid filling if 'draw_list->_CmdHeader.ClipRect.Overlays(points_bb) == false') void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col) { if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; - // coarse culling against viewport to avoid processing triangles outside of the visible area - ImVec2 bounds_min = ImVec2(FLT_MAX, FLT_MAX); - ImVec2 bounds_max = ImVec2(-FLT_MAX, -FLT_MAX); - - for (int i = 0; i < points_count; ++i) - { - const ImVec2& pos = points[i]; - - bounds_min = ImMin(bounds_min, pos); - bounds_max = ImMax(bounds_max, pos); - } - - if (!ImRect(_ClipRectStack.back()).Overlaps(ImRect(bounds_min, bounds_max))) - return; - const ImVec2 uv = _Data->TexUvWhitePixel; - + ImTriangulator triangulator; + unsigned int triangle[3]; if (Flags & ImDrawListFlags_AntiAliasedFill) { // Anti-aliased Fill const float AA_SIZE = _FringeScale; const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const int idx_count = (points_count - 2)*3 + points_count * 6; + const int idx_count = (points_count - 2) * 3 + points_count * 6; const int vtx_count = (points_count * 2); PrimReserve(idx_count, vtx_count); @@ -2067,11 +1944,11 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); - ImTriangulator triangulator = ImTriangulator(points, points_count, _Data->TempBuffer.Data); - while (triangulator.HasNext()) + triangulator.Init(points, points_count, _Data->TempBuffer.Data); + while (triangulator._TrianglesLeft > 0) { - ImTriangulator::Triangle triangle = triangulator.Next(); - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[2] << 1)); + triangulator.GetNextTriangle(triangle); + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle[2] << 1)); _IdxWritePtr += 3; } @@ -2115,7 +1992,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou else { // Non Anti-aliased Fill - const int idx_count = (points_count - 2)*3; + const int idx_count = (points_count - 2) * 3; const int vtx_count = points_count; PrimReserve(idx_count, vtx_count); for (int i = 0; i < vtx_count; i++) @@ -2124,11 +2001,11 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou _VtxWritePtr++; } _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); - ImTriangulator triangulator = ImTriangulator(points, points_count, _Data->TempBuffer.Data); - while (triangulator.HasNext()) + triangulator.Init(points, points_count, _Data->TempBuffer.Data); + while (triangulator._TrianglesLeft > 0) { - ImTriangulator::Triangle triangle = triangulator.Next(); - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[2]); + triangulator.GetNextTriangle(triangle); + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle[2]); _IdxWritePtr += 3; } _VtxCurrentIdx += (ImDrawIdx)vtx_count; diff --git a/imgui_internal.h b/imgui_internal.h index 610db346..2039f748 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -498,7 +498,8 @@ IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); -inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } +inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } +inline bool ImTriangleIsClockwise(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0f; } // Helper: ImVec1 (1D vector) // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches) From 6b7358e9f36b7faa37ae40704aafc4341eb62020 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 4 Mar 2024 11:30:22 +0100 Subject: [PATCH 7/8] InputText: adding clarifying note about ImGuiInputTextCallbackData::Buf. (#7363) --- imgui.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui.h b/imgui.h index 598f565a..eab7c57e 100644 --- a/imgui.h +++ b/imgui.h @@ -2268,6 +2268,8 @@ struct ImGuiInputTextCallbackData void* UserData; // What user passed to InputText() // Read-only // Arguments for the different callback events + // - During Resize callback, Buf will be same as your input buffer. + // - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback. // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary. // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0; From 65dc67f63c60201d18d3ab3b26f2595ce6f3ed18 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 5 Mar 2024 17:34:34 +0100 Subject: [PATCH 8/8] Windows: Double-click to collapse may be disabled via key-ownership mechanism. (#7369) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0cb8982e..050bde82 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,7 @@ Other changes: - Windows: Scrollbar visibility decision uses current size when both size and contents size are submitted by API. (#7252) +- Windows: Double-click to collapse may be disabled via key-ownership mechanism. (#7369) - Menus, Popups: Fixed an issue where sibling menu popups re-opening in successive frames would erroneously close the window. While it is technically a popup issue it would generally manifest when fast moving the mouse bottom to top in a sub-menu. diff --git a/imgui.cpp b/imgui.cpp index 31c6d251..a4ecdefe 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6629,8 +6629,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. ImRect title_bar_rect = window->TitleBarRect(); - if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseClickedCount[0] == 2) - window->WantCollapseToggle = true; + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)) + if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_None) + window->WantCollapseToggle = true; if (window->WantCollapseToggle) { window->Collapsed = !window->Collapsed;