TreeNode: rename/rework ImGuiNavTreeNodeData system to be usable by more features. (#2920, #1131, #7553)

Reworked to it is easier during TreeNode code to request extra data to be stored.
This commit is contained in:
ocornut 2024-07-03 18:25:01 +02:00
parent 0a73c6ec3f
commit 2d0baaabe6
4 changed files with 57 additions and 32 deletions

View File

@ -3795,7 +3795,7 @@ void ImGui::Shutdown()
g.FontStack.clear(); g.FontStack.clear();
g.OpenPopupStack.clear(); g.OpenPopupStack.clear();
g.BeginPopupStack.clear(); g.BeginPopupStack.clear();
g.NavTreeNodeStack.clear(); g.TreeNodeStack.clear();
g.Viewports.clear_delete(); g.Viewports.clear_delete();
@ -7131,7 +7131,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->DC.MenuBarAppending = false; window->DC.MenuBarAppending = false;
window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user);
window->DC.TreeDepth = 0; window->DC.TreeDepth = 0;
window->DC.TreeJumpToParentOnPopMask = 0x00; window->DC.TreeHasStackDataDepthMask = 0x00;
window->DC.ChildWindows.resize(0); window->DC.ChildWindows.resize(0);
window->DC.StateStorage = &window->StateStorage; window->DC.StateStorage = &window->StateStorage;
window->DC.CurrentColumns = NULL; window->DC.CurrentColumns = NULL;
@ -12039,7 +12039,7 @@ void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result)
} }
// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere // Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere
void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiNavTreeNodeData* tree_node_data) void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
g.NavMoveScoringItems = false; g.NavMoveScoringItems = false;

View File

@ -953,6 +953,7 @@ static void ShowDemoWindowWidgets()
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only.");
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap);
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)");
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsBackHere", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere);
ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position);
ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop);
ImGui::Text("Hello!"); ImGui::Text("Hello!");
@ -985,7 +986,7 @@ static void ShowDemoWindowWidgets()
ImGui::Text("This is a drag and drop source"); ImGui::Text("This is a drag and drop source");
ImGui::EndDragDropSource(); ImGui::EndDragDropSource();
} }
if (i == 2) if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanTextWidth))
{ {
// Item 2 has an additional inline button to help demonstrate SpanTextWidth. // Item 2 has an additional inline button to help demonstrate SpanTextWidth.
ImGui::SameLine(); ImGui::SameLine();
@ -994,6 +995,8 @@ static void ShowDemoWindowWidgets()
if (node_open) if (node_open)
{ {
ImGui::BulletText("Blah blah\nBlah Blah"); ImGui::BulletText("Blah blah\nBlah Blah");
ImGui::SameLine();
ImGui::SmallButton("Button");
ImGui::TreePop(); ImGui::TreePop();
} }
} }

View File

@ -135,7 +135,6 @@ struct ImGuiLastItemData; // Status storage for last submitted items
struct ImGuiLocEntry; // A localization entry. struct ImGuiLocEntry; // A localization entry.
struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only
struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result
struct ImGuiNavTreeNodeData; // Temporary storage for last TreeNode() being a Left arrow landing candidate.
struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions
struct ImGuiNextWindowData; // Storage for SetNextWindow** functions struct ImGuiNextWindowData; // Storage for SetNextWindow** functions
struct ImGuiNextItemData; // Storage for SetNextItem** functions struct ImGuiNextItemData; // Storage for SetNextItem** functions
@ -154,6 +153,7 @@ struct ImGuiTableInstanceData; // Storage for one instance of a same table
struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables.
struct ImGuiTableSettings; // Storage for a table .ini settings struct ImGuiTableSettings; // Storage for a table .ini settings
struct ImGuiTableColumnsSettings; // Storage for a column .ini settings struct ImGuiTableColumnsSettings; // Storage for a column .ini settings
struct ImGuiTreeNodeStackData; // Temporary storage for TreeNode().
struct ImGuiTypingSelectState; // Storage for GetTypingSelectRequest() struct ImGuiTypingSelectState; // Storage for GetTypingSelectRequest()
struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aimed to be public) struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aimed to be public)
struct ImGuiWindow; // Storage for one window struct ImGuiWindow; // Storage for one window
@ -1251,14 +1251,16 @@ struct ImGuiLastItemData
ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } ImGuiLastItemData() { memset(this, 0, sizeof(*this)); }
}; };
// Store data emitted by TreeNode() for usage by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere. // Store data emitted by TreeNode() for usage by TreePop()
// This is the minimum amount of data that we need to perform the equivalent of NavApplyItemToResult() and which we can't infer in TreePop() // - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data
// Only stored when the node is a potential candidate for landing on a Left arrow jump. // which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult().
struct ImGuiNavTreeNodeData // Only stored when the node is a potential candidate for landing on a Left arrow jump.
struct ImGuiTreeNodeStackData
{ {
ImGuiID ID; ImGuiID ID;
ImGuiItemFlags InFlags; ImGuiTreeNodeFlags TreeFlags;
ImRect NavRect; ImGuiItemFlags InFlags; // Used for nav landing
ImRect NavRect; // Used for nav landing
}; };
struct IMGUI_API ImGuiStackSizes struct IMGUI_API ImGuiStackSizes
@ -2038,7 +2040,7 @@ struct ImGuiContext
ImVector<ImGuiGroupData> GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVector<ImGuiGroupData> GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin()
ImVector<ImGuiPopupData> OpenPopupStack; // Which popups are open (persistent) ImVector<ImGuiPopupData> OpenPopupStack; // Which popups are open (persistent)
ImVector<ImGuiPopupData> BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) ImVector<ImGuiPopupData> BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame)
ImVector<ImGuiNavTreeNodeData> NavTreeNodeStack; // Stack for TreeNode() when a NavLeft requested is emitted. ImVector<ImGuiTreeNodeStackData>TreeNodeStack; // Stack for TreeNode()
// Viewports // Viewports
ImVector<ImGuiViewportP*> Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData. ImVector<ImGuiViewportP*> Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData.
@ -2503,7 +2505,7 @@ struct IMGUI_API ImGuiWindowTempData
ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs.
ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement
int TreeDepth; // Current tree depth. int TreeDepth; // Current tree depth.
ImU32 TreeJumpToParentOnPopMask; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31.. Could be turned into a ImU64 if necessary. ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary.
ImVector<ImGuiWindow*> ChildWindows; ImVector<ImGuiWindow*> ChildWindows;
ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state)
ImGuiOldColumns* CurrentColumns; // Current columns set ImGuiOldColumns* CurrentColumns; // Current columns set
@ -3201,7 +3203,7 @@ namespace ImGui
IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags);
IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags);
IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result); IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result);
IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiNavTreeNodeData* tree_node_data); IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data);
IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestCancel();
IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestApplyResult();
IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags);

View File

@ -6310,6 +6310,21 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
return is_open; return is_open;
} }
// Store ImGuiTreeNodeStackData for just submitted node.
static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back();
tree_node_data->ID = g.LastItemData.ID;
tree_node_data->TreeFlags = flags;
tree_node_data->InFlags = g.LastItemData.InFlags;
tree_node_data->NavRect = g.LastItemData.NavRect;
window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
}
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
{ {
ImGuiWindow* window = GetCurrentWindow(); ImGuiWindow* window = GetCurrentWindow();
@ -6379,20 +6394,19 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) bool store_tree_node_stack_data = false;
if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
{ {
g.NavTreeNodeStack.resize(g.NavTreeNodeStack.Size + 1); if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive)
ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back(); if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
nav_tree_node_data->ID = id; store_tree_node_stack_data = true;
nav_tree_node_data->InFlags = g.LastItemData.InFlags; }
nav_tree_node_data->NavRect = g.LastItemData.NavRect;
window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
}
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
if (!item_add) if (!item_add)
{ {
if (store_tree_node_stack_data && is_open)
TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
TreePushOverrideID(id); TreePushOverrideID(id);
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
@ -6533,8 +6547,11 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
else else
RenderText(text_pos, label, label_end, false); RenderText(text_pos, label, label_end, false);
if (store_tree_node_stack_data && is_open)
TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
TreePushOverrideID(id); TreePushOverrideID(id);
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
return is_open; return is_open;
} }
@ -6573,16 +6590,19 @@ void ImGui::TreePop()
window->DC.TreeDepth--; window->DC.TreeDepth--;
ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
// Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request
if (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask) // Only set during request
{ {
ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back(); ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back();
IM_ASSERT(nav_tree_node_data->ID == window->IDStack.back()); IM_ASSERT(data->ID == window->IDStack.back());
if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere)
NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, nav_tree_node_data); {
g.NavTreeNodeStack.pop_back(); // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
}
g.TreeNodeStack.pop_back();
window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
} }
window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1;
IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
PopID(); PopID();