Icons in the tree #282

Closed
opened 2015-07-22 11:55:50 +00:00 by emoon · 11 comments
emoon commented 2015-07-22 11:55:50 +00:00 (Migrated from github.com)

Hi,

I wonder if it is currently possible to have custom icons in the tree view? (that would be infront of the text and for the folded/unfolded state)

Think of something like an explorer view/Visual Studio Workspace of files view

Hi, I wonder if it is currently possible to have custom icons in the tree view? (that would be infront of the text and for the folded/unfolded state) Think of something like an explorer view/Visual Studio Workspace of files view
ocornut commented 2015-07-22 15:43:11 +00:00 (Migrated from github.com)

There's a few way you can do that but the correct one will have to wait until I make a change to TreeNode - I started it but shelved it.

Currently if you do TreeNode("##hiddenlabel", ...); SameLine(0, 0); you can already add stuff.

treenode sameline

There's will several issues:

  • Tree node will only react to click over roughly the width of the label (this was intentional to allow other widgets such as buttons to be used on the same line).
  • Undesired horizontal spacing (visible here)
  • If your widget have bigger height, the tree node won't be correctly vertically aligned.

So what we need to do is:

  • Make it an option up to decide the width of the hit box for input processing (up to label or up to a certain X position, typically edge of the screen but it can be shorter also). I don't yet know what form this option can take, how it'll be specified and stored.
  • Fix the undesired horizontal spacing.
  • Fix the height issues, not sure what's the correct way here. User may want to adjust the tree node height to match other widget height in some instances (e.g. adding a bigger icon) but not in other instances (e.g. adding a tree node on the side of a very large widget), so it may be an option as well or maybe to simplify the height can be capped to widget-height by default so the two typical height will be text-height and widget-height.

You can temporarily hack around your way from those problems by passing a tree node label full of spaces to get a larger hit box, and moving the cursor SetCursorPosX(GetCursorPosX() - 20.0f) after SameLine.

There's a few way you can do that but the correct one will have to wait until I make a change to TreeNode - I started it but shelved it. Currently if you do `TreeNode("##hiddenlabel", ...); SameLine(0, 0);` you can already add stuff. ![treenode sameline](https://cloud.githubusercontent.com/assets/8225057/8829685/2dd8552e-3055-11e5-9a01-ed1681f3d1b6.PNG) There's will several issues: - Tree node will only react to click over roughly the width of the label (this was intentional to allow other widgets such as buttons to be used on the same line). - Undesired horizontal spacing (visible here) - If your widget have bigger height, the tree node won't be correctly vertically aligned. So what we need to do is: - Make it an option up to decide the width of the hit box for input processing (up to label or up to a certain X position, typically edge of the screen but it can be shorter also). I don't yet know what form this option can take, how it'll be specified and stored. - Fix the undesired horizontal spacing. - Fix the height issues, not sure what's the correct way here. User may want to adjust the tree node height to match other widget height in some instances (e.g. adding a bigger icon) but not in other instances (e.g. adding a tree node on the side of a very large widget), so it may be an option as well or maybe to simplify the height can be capped to widget-height by default so the two typical height will be text-height and widget-height. You can temporarily hack around your way from those problems by passing a tree node label full of spaces to get a larger hit box, and moving the cursor `SetCursorPosX(GetCursorPosX() - 20.0f)` after SameLine.
emoon commented 2015-07-22 16:50:11 +00:00 (Migrated from github.com)

Thanks for the reply.

I will go for your temporary hack for now.

Thanks for the reply. I will go for your temporary hack for now.
ocornut commented 2015-07-22 17:26:52 +00:00 (Migrated from github.com)

If you use spaces for id remember to do a prior PushId/PopId to make the full-id unique.

If you use spaces for id remember to do a prior PushId/PopId to make the full-id unique.
emoon commented 2015-07-22 18:00:16 +00:00 (Migrated from github.com)

Will do. Thanks!

Will do. Thanks!
ocornut commented 2015-08-29 13:13:26 +00:00 (Migrated from github.com)

I wonder if it is currently possible to have custom icons in the tree view? (that would be infront of the text and for the folded/unfolded state)

Did you mean you wanted to remove the ">" thingie and draw an icon instead?

> I wonder if it is currently possible to have custom icons in the tree view? (that would be infront of the text and for the folded/unfolded state) Did you mean you wanted to remove the ">" thingie and draw an icon instead?
ocornut commented 2015-08-29 13:46:02 +00:00 (Migrated from github.com)

Btw you can rewrite a pretty much feature-identical custom tree node this way.
The main problem with that approach is that it is probably 10+ times slower than the version that pokes at data structure directly. (haven't measure, just guessed). Because of going through those public apis. Using imgui_internal.h would be beneficial.

ImGui::Begin("Custom Tree Node");

bool MyTreeNode(const char* label)
{
    const ImGuiStyle& style = ImGui::GetStyle();
    ImGuiStorage* storage = ImGui::GetStateStorage();

    ImU32 id = ImGui::GetID(label);
    int opened = storage->GetInt(id, 0);
    float x = ImGui::GetCursorPosX();
    ImGui::BeginGroup();
    if (ImGui::InvisibleButton(label, ImVec2(-1, ImGui::GetWindowFontSize()+style.FramePadding.y*2)))
    {
        int* p_opened = storage->GetIntRef(id, 0);
        opened = *p_opened = !*p_opened;
    }
    bool hovered = ImGui::IsItemHovered();
    bool active = ImGui::IsItemActive();
    if (hovered || active)
        ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemBoxMin(), ImGui::GetItemBoxMax(), ImColor(ImGui::GetStyle().Colors[active ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered]));

    // Icon, text
    ImGui::SameLine(x);
    ImGui::ColorButton(opened ? ImColor(255,0,0) : ImColor(0,255,0));
    ImGui::SameLine();
    ImGui::Text(label);
    ImGui::EndGroup();
    if (opened)
        ImGui::TreePush(label);
    return opened != 0;
};

if (MyTreeNode("Hello"))
{
    ImGui::Text("blah");
    if (mytreenode("blah"))
    {
        ImGui::Text("Hello");
        ImGui::TreePop();
    }
    ImGui::Text("blah");
    ImGui::TreePop();
}

ImGui::End();

(gifcam is creating undesired transparent color in the GIF, the color square should be green when it's not red)
custom tree node

Btw you can rewrite a pretty much feature-identical custom tree node this way. The main problem with that approach is that it is probably 10+ times slower than the version that pokes at data structure directly. (haven't measure, just guessed). Because of going through those public apis. Using imgui_internal.h would be beneficial. ``` ImGui::Begin("Custom Tree Node"); bool MyTreeNode(const char* label) { const ImGuiStyle& style = ImGui::GetStyle(); ImGuiStorage* storage = ImGui::GetStateStorage(); ImU32 id = ImGui::GetID(label); int opened = storage->GetInt(id, 0); float x = ImGui::GetCursorPosX(); ImGui::BeginGroup(); if (ImGui::InvisibleButton(label, ImVec2(-1, ImGui::GetWindowFontSize()+style.FramePadding.y*2))) { int* p_opened = storage->GetIntRef(id, 0); opened = *p_opened = !*p_opened; } bool hovered = ImGui::IsItemHovered(); bool active = ImGui::IsItemActive(); if (hovered || active) ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemBoxMin(), ImGui::GetItemBoxMax(), ImColor(ImGui::GetStyle().Colors[active ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered])); // Icon, text ImGui::SameLine(x); ImGui::ColorButton(opened ? ImColor(255,0,0) : ImColor(0,255,0)); ImGui::SameLine(); ImGui::Text(label); ImGui::EndGroup(); if (opened) ImGui::TreePush(label); return opened != 0; }; if (MyTreeNode("Hello")) { ImGui::Text("blah"); if (mytreenode("blah")) { ImGui::Text("Hello"); ImGui::TreePop(); } ImGui::Text("blah"); ImGui::TreePop(); } ImGui::End(); ``` (gifcam is creating undesired transparent color in the GIF, the color square should be green when it's not red) ![custom tree node](https://cloud.githubusercontent.com/assets/8225057/9562183/45cf67c4-4e5c-11e5-8e08-84ce1d44fd30.gif)
ocornut commented 2015-08-29 14:01:37 +00:00 (Migrated from github.com)

And that's the exact same feature but using some internal data structure.

#include <imgui_internal.h>
bool MyTreeNode(const char* label)
{
    ImGuiState& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;

    ImU32 id = window->GetID(label);
    int opened = window->StateStorage.GetInt(id, 0);
    ImVec2 pos = window->DC.CursorPos;
    if (ImGui::InvisibleButton(label, ImVec2(-1, g.FontSize + g.Style.FramePadding.y*2)))
    {
        int* p_opened = window->StateStorage.GetIntRef(id, 0);
        opened = *p_opened = !*p_opened;
    }
    bool hovered = ImGui::IsItemHovered();
    bool active = ImGui::IsItemActive();
    if (hovered || active)
        window->DrawList->AddRectFilled(window->DC.LastItemRect.Min, window->DC.LastItemRect.Max, window->Color(active ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered));

    // Icon, text
    float button_sz = g.FontSize + g.Style.FramePadding.y*2;
    window->DrawList->AddRectFilled(pos, ImVec2(pos.x+button_sz, pos.y+button_sz), opened ? ImColor(255,0,0) : ImColor(0,255,0));
    ImGui::RenderText(ImVec2(pos.x + button_sz + g.Style.ItemInnerSpacing.x, pos.y + g.Style.FramePadding.y), label);
    if (opened)
        ImGui::TreePush(label);
    return opened != 0;
}
And that's the exact same feature but using some internal data structure. ``` #include <imgui_internal.h> bool MyTreeNode(const char* label) { ImGuiState& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImU32 id = window->GetID(label); int opened = window->StateStorage.GetInt(id, 0); ImVec2 pos = window->DC.CursorPos; if (ImGui::InvisibleButton(label, ImVec2(-1, g.FontSize + g.Style.FramePadding.y*2))) { int* p_opened = window->StateStorage.GetIntRef(id, 0); opened = *p_opened = !*p_opened; } bool hovered = ImGui::IsItemHovered(); bool active = ImGui::IsItemActive(); if (hovered || active) window->DrawList->AddRectFilled(window->DC.LastItemRect.Min, window->DC.LastItemRect.Max, window->Color(active ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered)); // Icon, text float button_sz = g.FontSize + g.Style.FramePadding.y*2; window->DrawList->AddRectFilled(pos, ImVec2(pos.x+button_sz, pos.y+button_sz), opened ? ImColor(255,0,0) : ImColor(0,255,0)); ImGui::RenderText(ImVec2(pos.x + button_sz + g.Style.ItemInnerSpacing.x, pos.y + g.Style.FramePadding.y), label); if (opened) ImGui::TreePush(label); return opened != 0; } ```
ocornut commented 2015-08-29 14:07:30 +00:00 (Migrated from github.com)

Perhaps I can turn part of this, or actually the underlying behavior which does a little more, into a TreeNodeBehavior().

Perhaps I can turn part of this, or actually the underlying behavior which does a little more, into a TreeNodeBehavior().
ocornut commented 2015-08-29 17:21:12 +00:00 (Migrated from github.com)

Made some change so you can write a custom tree node that is more accurate in term of honoring Auto-Expand-Node-On-Logging and SetNextTreeNode** calls.

#include <imgui_internal.h>

bool MyTreeNode(const char* label)
{
    ImGuiState& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;

    ImU32 id = window->GetID(label);
    ImVec2 pos = window->DC.CursorPos;
    ImRect bb(pos, ImVec2(pos.x + ImGui::GetContentRegionAvail().x, pos.y + g.FontSize + g.Style.FramePadding.y*2));
    bool opened = ImGui::TreeNodeBehaviorIsOpened(id);
    bool hovered, held;
    if (ImGui::ButtonBehavior(bb, id, &hovered, &held, true))
        window->DC.StateStorage->SetInt(id, opened ? 0 : 1);
    if (hovered || held)
        window->DrawList->AddRectFilled(bb.Min, bb.Max, window->Color(held ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered));

    // Icon, text
    float button_sz = g.FontSize + g.Style.FramePadding.y*2;
    window->DrawList->AddRectFilled(pos, ImVec2(pos.x+button_sz, pos.y+button_sz), opened ? ImColor(255,0,0) : ImColor(0,255,0));
    ImGui::RenderText(ImVec2(pos.x + button_sz + g.Style.ItemInnerSpacing.x, pos.y + g.Style.FramePadding.y), label);

    ImGui::ItemSize(bb, g.Style.FramePadding.y);
    ImGui::ItemAdd(bb, &id);

    if (opened)
        ImGui::TreePush(label);
    return opened;
}

Until I add a mechanism to parametrize how far the hit-rectangle of a treenode.should reach, this is probably the best way to just make something fully custom, Not particularly great looking but it's not the type of code you would often look back at.

Made some change so you can write a custom tree node that is more accurate in term of honoring Auto-Expand-Node-On-Logging and SetNextTreeNode*\* calls. ``` #include <imgui_internal.h> bool MyTreeNode(const char* label) { ImGuiState& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImU32 id = window->GetID(label); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, ImVec2(pos.x + ImGui::GetContentRegionAvail().x, pos.y + g.FontSize + g.Style.FramePadding.y*2)); bool opened = ImGui::TreeNodeBehaviorIsOpened(id); bool hovered, held; if (ImGui::ButtonBehavior(bb, id, &hovered, &held, true)) window->DC.StateStorage->SetInt(id, opened ? 0 : 1); if (hovered || held) window->DrawList->AddRectFilled(bb.Min, bb.Max, window->Color(held ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered)); // Icon, text float button_sz = g.FontSize + g.Style.FramePadding.y*2; window->DrawList->AddRectFilled(pos, ImVec2(pos.x+button_sz, pos.y+button_sz), opened ? ImColor(255,0,0) : ImColor(0,255,0)); ImGui::RenderText(ImVec2(pos.x + button_sz + g.Style.ItemInnerSpacing.x, pos.y + g.Style.FramePadding.y), label); ImGui::ItemSize(bb, g.Style.FramePadding.y); ImGui::ItemAdd(bb, &id); if (opened) ImGui::TreePush(label); return opened; } ``` Until I add a mechanism to parametrize how far the hit-rectangle of a treenode.should reach, this is probably the best way to just make something fully custom, Not particularly great looking but it's not the type of code you would often look back at.
emoon commented 2015-08-30 07:56:03 +00:00 (Migrated from github.com)

Neat :) Thanks!

Neat :) Thanks!
ocornut commented 2015-12-25 11:09:06 +00:00 (Migrated from github.com)

Closing this afaik the original post is answered. Ideally we'd simplify the code for MyTreeNode() further and improve forward compatibility, but it's not too bad now.

(EDIT adding midly related attachment after the fact so I can link to it from another forum)
layers

Closing this afaik the original post is answered. Ideally we'd simplify the code for MyTreeNode() further and improve forward compatibility, but it's not too bad now. (_EDIT_ adding midly related attachment after the fact so I can link to it from another forum) ![layers](https://cloud.githubusercontent.com/assets/8225057/12251029/71fb6f88-b8c3-11e5-9d13-59b31b926f1c.PNG)
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#282
No description provided.