Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to recursively fold all TreeNodes? #1131

Open
liorda opened this issue May 4, 2017 · 9 comments
Open

How to recursively fold all TreeNodes? #1131

liorda opened this issue May 4, 2017 · 9 comments
Labels
tree tree nodes

Comments

@liorda
Copy link

liorda commented May 4, 2017

I can use ImGui::SetNextTreeNodeOpen(false);, but if I do, the node's children won't be expanded so I won't be able set their open state...
Is there anyway to access the internal tree behavior?
Thanks!

@ratchetfreak
Copy link

don't call it every frame

@liorda
Copy link
Author

liorda commented May 5, 2017

@ratchetfreak I don't understand. I'm already not calling it every frame, only when the user clicks the "fold tree" button.

@ocornut
Copy link
Owner

ocornut commented May 5, 2017

@liorda Please explain what you are trying to do in more clarity, it is hard to understand at the moment.

  1. Each TreeNode stores an integer in the window storage when clicked. Each TreeNode push the its identifier to the id stack for children tree nodes would be the combinaison of those stacked identifiers. Based on that you can infer the full ID of any of your children if you know their path.

So ImGui::GetStateStorage()->SetInt(id, 0) would effectively close a node. With e.g. id = ImGui::GetId("treenodename") for a direct children.

Except this will create many '0' entries in the storage for tree nodes that haven't been touched, so it will grow the storage unnecessarily, which is not desirable. So you can do if (GetId(id) == 1) SetId(id, 0).
(We are missing a int* GetIntRefIfExist(ImGuiIdKey key) function to allow doing that optimally (only closing nodes that were opened). But that solution above will work. )

  1. You may swap the underlying storage for a given tree block using SetStateStorage() to your own storage, and then call SetAllInt() on your own storage, but this is sort of tricky/risky if not done well because any other user of that same storage will get data effected.

(
3. We are missing a larger concept of being able to blindly opening into a hierarchy while ignoring the render. This is not trivial to implement, we will implement some form of that for the Shortcuts API #456, And then it might extend into having helpers to magically have TreeNode() return true while not existing to allow your code to run. Note that trees can perfectly be recursive etc. so it's not exactly a trivial thing to do safely, and it's probably not what you need now. I'm just musing around with future ideas here.
)

@liorda
Copy link
Author

liorda commented May 7, 2017

Sorry for being vague, I'll try to explain better:
I have an hierarchy of objects, which I show on screen via simple recursion:
void Node::Show() { if (TreeNode(mName)) { for (child : mChildren) child->Show(); } }.
Whenever I fold/expand any node, its children "remember" their open/closed state, and that's pretty cool.
Now, I implemented an "Expand all" button, which was very easy, by just calling SetNextTreeNodeOpen(true) before calling TreeNode() in the recursive function. So far so good. The problem is that for implementing a recursive "fold all", I can't use that. If I call SetNextTreeNodeOpen(false), the TreeNode() call returns false, so the children are rendered / pushed to the id stack.

@ocornut I wasn't aware of ImGui::GetStateStorage()->Set*(), but I'm not sure I want to go this route since it would be hard finding out the current context data. Your 3rd point is exactly the issue I'm trying to solve. I guess I'll try to solve it myself, but I'll try to do it in a generic way.

@liorda
Copy link
Author

liorda commented May 7, 2017

OK, that was pretty easy. All I needed to do was change the Show function to:

void Node::Show() {
  if (foldAll)
    SetNextTreeNodeOpen(false);
  if (TreeNode(mName) || foldAll) {
    if (foldAll)
      TreePush(mName);
    for (child : mChildren)
      child->Show();
  }
}

I have no idea what's the best way to allow this with the current api style. Maybe a new flag to ImGuiTreeNodeFlags_? or a new state value to ImGuiContext::SetNextTreeNodeOpenVal and a BeginFold()/EndFold()?

@ocornut ocornut added the tree tree nodes label Jun 15, 2018
@Jarhmander
Copy link
Contributor

I tried the solution proposed by @liorda, but in my test (Linux, SDL/OpenGL2, as I'm still on old hardware), it does a quick, one frame "glitch" where all the nodes are temporarily all open. This is most noticeable when the tree is already collapsed.

To test, paste that code in the demo or other working code:


// Oh noes a global!
bool collapse = false;

template <typename F>
 void do_tree(const char *l, F &&f)
{
    using namespace ImGui;
    if (collapse)
        SetNextTreeNodeOpen(false);
    if (TreeNode(l) || collapse) {
        if (collapse)
            TreePush(l);
        f();
        TreePop();
    }
}

int main(...)
{
    ...
        {
            ImGui::Begin("Test tree");
            collapse = ImGui::Button("collapse all");
            do_tree("A", [&]() {
                do_tree("B", [&]() { 
                    ImGui::Text("a1");
                });
                do_tree("C", [&]() { 
                    ImGui::Text("foo");
                    do_tree("D", [&]() {
                        ImGui::Text("top");
                    });
                });
            });
            ImGui::End();
        }
        ...
}

Is there an equivalent way to achieve the same effect without this "glitch"?

@pesalasilva
Copy link

I want to add a tree view with the default open and I want it to collapse or hide when clicking over a tree node. Since I have to keep all opened I set ImGui::SetNextItemOpen(true); for every instance. Then I cannot get the functionality of hiding the view.
Is anyone have a suggestion on this. My code snippet is this
ImGui::SetNextItemOpen(true); if(ImGui::TreeNode("option1")) { ImGui::Indent(); auto missionStartingBtn = ImGui::SmallButton("option1"); if(missionStartingBtn) { } auto missionEndBtn = ImGui::SmallButton("option2"); if(missionEndBtn) { } ImGui::Unindent(); ImGui::TreePop(); }

ocornut added a commit that referenced this issue Aug 8, 2023
…Here now goes through proper navigation logic: honor scrolling and selection. (#1079, #1131)

Added a stack for this purpose which other features might build on (e.g. #2920). However this is currently gated by many tests and not a performance concern, but making stack happen all the time may be undesirable.
Ajblast pushed a commit to Ajblast/imgui that referenced this issue Aug 9, 2023
…Here now goes through proper navigation logic: honor scrolling and selection. (ocornut#1079, ocornut#1131)

Added a stack for this purpose which other features might build on (e.g. ocornut#2920). However this is currently gated by many tests and not a performance concern, but making stack happen all the time may be undesirable.
@Jonathan-L-Davis
Copy link

@ocornut Sorry for the nitpick, did you mean issue #456, instead of #45?

@lfxu
Copy link

lfxu commented Feb 16, 2024

I suggest adding a way to process a tree node without showing it, either via a flag or sth like TreeNodeExHidden(), or even just HideNextItem().

This solution is generic to all similar problems. For example, Filtering a tree, i.e., hiding all nodes of intermediate levels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tree tree nodes
Projects
None yet
Development

No branches or pull requests

7 participants