mirror of
https://github.com/ocornut/imgui.git
synced 2026-02-27 22:05:08 +00:00
TreeNode, Demo: Property Editor: demonstrate a way to perform tree clipping by fast-forwarding through non-visible chunks. (#3823, #9251, #6990, #6042)
This commit is contained in:
@@ -53,6 +53,10 @@ Other Changes:
|
||||
- Moved TreeNodeGetOpen() helper to public API. I was hesitant to make this public
|
||||
because I intend to provide a more generic and feature-full version, but in the meanwhile
|
||||
this will do. (#3823, #9251, #7553, #6754, #5423, #2958, #2079, #1947, #1131, #722)
|
||||
- In 'Demo->Property Editor' demonstrate a way to perform tree clipping by fast-forwarding
|
||||
through non-visible chunks. (#3823, #9251, #6990, #6042)
|
||||
Using SetNextItemStorageID() + TreeNodeGetOpen() makes this notably easier than
|
||||
it was prior to 1.91.
|
||||
- Style: border sizes are now scaled (and rounded) by ScaleAllSizes().
|
||||
- Clipper:
|
||||
- Clear `DisplayStart`/`DisplayEnd` fields when `Step()` returns false.
|
||||
|
||||
115
imgui_demo.cpp
115
imgui_demo.cpp
@@ -9449,14 +9449,19 @@ struct ExampleAppPropertyEditor
|
||||
{
|
||||
ImGuiTextFilter Filter;
|
||||
ExampleTreeNode* SelectedNode = NULL;
|
||||
bool UseClipper = false;
|
||||
|
||||
void Draw(ExampleTreeNode* root_node)
|
||||
{
|
||||
// Left side: draw tree
|
||||
// - Currently using a table to benefit from RowBg feature
|
||||
// - Our tree node are all of equal height, facilitating the use of a clipper.
|
||||
if (ImGui::BeginChild("##tree", ImVec2(300, 0), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))
|
||||
{
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
|
||||
ImGui::Checkbox("Use Clipper", &UseClipper);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(%d root nodes)", root_node->Childs.Size);
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
|
||||
if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll))
|
||||
@@ -9465,7 +9470,10 @@ struct ExampleAppPropertyEditor
|
||||
|
||||
if (ImGui::BeginTable("##list", 1, ImGuiTableFlags_RowBg))
|
||||
{
|
||||
DrawTree(root_node);
|
||||
if (UseClipper)
|
||||
DrawClippedTree(root_node);
|
||||
else
|
||||
DrawTree(root_node);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
@@ -9540,19 +9548,99 @@ struct ExampleAppPropertyEditor
|
||||
|
||||
// Custom search filter
|
||||
// - Here we apply on root node only.
|
||||
// - This does a stristr which is pretty heavy. In a real large-scale app you would likely store a filtered list which in turns would be trivial to linearize.
|
||||
// - This does a case insensitive stristr which is pretty heavy. In a real large-scale app you would likely store a filtered list which in turns would be trivial to linearize.
|
||||
inline bool IsNodePassingFilter(ExampleTreeNode* node)
|
||||
{
|
||||
return node->Parent->Parent != NULL || Filter.PassFilter(node->Name);
|
||||
}
|
||||
|
||||
// Basic version, recursive. This is how you would generally draw a tree.
|
||||
// - Simple but going to be noticeably costly if you have a large amount of nodes as DrawTreeNode() is called for all of them.
|
||||
// - On my desktop PC (2020), for 10K nodes in an optimized build this takes ~1.2 ms
|
||||
// - Unlike arrays or grids which are very easy to clip, trees are currently more difficult to clip.
|
||||
void DrawTree(ExampleTreeNode* node)
|
||||
{
|
||||
for (ExampleTreeNode* child : node->Childs)
|
||||
if (Filter.PassFilter(child->Name)) // Filter root node
|
||||
DrawTreeNode(child);
|
||||
if (IsNodePassingFilter(child) && DrawTreeNode(child))
|
||||
{
|
||||
DrawTree(child);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTreeNode(ExampleTreeNode* node)
|
||||
// More advanced version. Use a alternative clipping technique: fast-forwarding through non-visible chunks.
|
||||
// - On my desktop PC (2020), for 10K nodes in an optimized build this takes ~0.1 ms
|
||||
// (in ExampleTree_CreateDemoTree(), change 'int ROOT_ITEMS_COUNT = 10000' to try with this amount of root nodes).
|
||||
// - 1. Use clipper with indeterminate count (items_count = INT_MAX): we need to call SeekCursorForItem() at the end once we know the count.
|
||||
// - 2. Use SetNextItemStorageID() to specify ID used for open/close storage, making it easy to call TreeNodeGetOpen() on any arbitrary node.
|
||||
// - 3. Linearize tree during traversal: our tree data structure makes it easy to access sibling and parents.
|
||||
// - Unlike clipping for a regular array or grid which may be done using random access limited to visible areas,
|
||||
// this technique requires traversing most accessible nodes. This could be made more optimal with extra work,
|
||||
// but this is a decent simplicity<>speed trade-off.
|
||||
// See https://github.com/ocornut/imgui/issues/3823 for discussions about this.
|
||||
void DrawClippedTree(ExampleTreeNode* root_node)
|
||||
{
|
||||
ExampleTreeNode* node = root_node->Childs[0]; // First node
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(INT_MAX);
|
||||
while (clipper.Step())
|
||||
while (clipper.UserIndex < clipper.DisplayEnd && node != NULL)
|
||||
node = DrawClippedTreeNodeAndAdvanceToNext(&clipper, node);
|
||||
|
||||
// Keep going to count nodes and submit final count so we have a reliable scrollbar.
|
||||
// - One could consider caching this value and only refreshing it occasionally e.g. window is focused and an action occurs.
|
||||
// - Incorrect but cheap approximation would be to use 'clipper_current_idx = IM_MAX(clipper_current_idx, root_node->Childs.Size)' instead.
|
||||
// - If either of those is implemented, the general cost will approach zero when scrolling is at the top of the tree.
|
||||
while (node != NULL)
|
||||
node = DrawClippedTreeNodeAndAdvanceToNext(&clipper, node);
|
||||
//clipper.UserIndex = IM_MAX(clipper.UserIndex, root_node->Childs.Size); // <-- Cheap approximation instead of while() loop above.
|
||||
clipper.SeekCursorForItem(clipper.UserIndex);
|
||||
}
|
||||
|
||||
ExampleTreeNode* DrawClippedTreeNodeAndAdvanceToNext(ImGuiListClipper* clipper, ExampleTreeNode* node)
|
||||
{
|
||||
if (IsNodePassingFilter(node))
|
||||
{
|
||||
// Draw node if within visible range
|
||||
bool is_open = false;
|
||||
if (clipper->UserIndex >= clipper->DisplayStart && clipper->UserIndex < clipper->DisplayEnd)
|
||||
{
|
||||
is_open = DrawTreeNode(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_open = (node->Childs.Size > 0 && ImGui::TreeNodeGetOpen((ImGuiID)node->UID));
|
||||
if (is_open)
|
||||
ImGui::TreePush(node->Name);
|
||||
}
|
||||
clipper->UserIndex++;
|
||||
|
||||
// Next node: recurse into childs
|
||||
if (is_open)
|
||||
return node->Childs[0];
|
||||
}
|
||||
|
||||
// Next node: next sibling, otherwise move back to parent
|
||||
while (node != NULL)
|
||||
{
|
||||
if (node->IndexInParent + 1 < node->Parent->Childs.Size)
|
||||
return node->Parent->Childs[node->IndexInParent + 1];
|
||||
node = node->Parent;
|
||||
if (node->Parent == NULL)
|
||||
break;
|
||||
ImGui::TreePop();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// To support node with same name we incorporate node->UID into the item ID.
|
||||
// (this would more naturally be done using PushID(node->UID) + TreeNodeEx(node->Name, tree_flags),
|
||||
// but it would require in DrawClippedTreeNodeAndAdvanceToNext() to add PushID() before TreePush(), and PopID() after TreePop(),
|
||||
// so instead we use TreeNodeEx(node->UID, tree_flags, "%s", node->Name) here)
|
||||
bool DrawTreeNode(ExampleTreeNode* node)
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(node->UID);
|
||||
ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None;
|
||||
tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards
|
||||
tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsToParent; // Left arrow support
|
||||
@@ -9561,21 +9649,18 @@ struct ExampleAppPropertyEditor
|
||||
if (node == SelectedNode)
|
||||
tree_flags |= ImGuiTreeNodeFlags_Selected; // Draw selection highlight
|
||||
if (node->Childs.Size == 0)
|
||||
tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet;
|
||||
tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen; // Use _NoTreePushOnOpen + set is_open=false to avoid unnecessarily push/pop on leaves.
|
||||
if (node->DataMyBool == false)
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
|
||||
bool is_open = ImGui::TreeNodeEx("", tree_flags, "%s", node->Name);
|
||||
ImGui::SetNextItemStorageID((ImGuiID)node->UID); // Use node->UID as storage id
|
||||
bool is_open = ImGui::TreeNodeEx((void*)(intptr_t)node->UID, tree_flags, "%s", node->Name);
|
||||
if (node->Childs.Size == 0)
|
||||
is_open = false;
|
||||
if (node->DataMyBool == false)
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemFocused())
|
||||
SelectedNode = node;
|
||||
if (is_open)
|
||||
{
|
||||
for (ExampleTreeNode* child : node->Childs)
|
||||
DrawTreeNode(child);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::PopID();
|
||||
return is_open;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user