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

Tree View with Table in DearPyGui #2379

Open
Umbrella167 opened this issue Aug 20, 2024 · 4 comments
Open

Tree View with Table in DearPyGui #2379

Umbrella167 opened this issue Aug 20, 2024 · 4 comments
Labels
state: pending not addressed yet

Comments

@Umbrella167
Copy link

I would like to request the addition of a feature in DearPyGui that allows the creation of a tree view inside a table. This feature is inspired by ImGui's capability to create such a layout. Below is a reference implementation using ImGui:

cpp

复制
if (ImGui::TreeNode("Tree view"))
{
static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;

if (ImGui::BeginTable("3ways", 3, flags))
{
    ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
    ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
    ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 18.0f);
    ImGui::TableHeadersRow();

    struct MyTreeNode
    {
        const char*     Name;
        const char*     Type;
        int             Size;
        int             ChildIdx;
        int             ChildCount;
        static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes)
        {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            const bool is_folder = (node->ChildCount > 0);
            if (is_folder)
            {
                bool open = ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
                ImGui::TableNextColumn();
                ImGui::TextDisabled("--");
                ImGui::TableNextColumn();
                ImGui::TextUnformatted(node->Type);
                if (open)
                {
                    for (int child_n = 0; child_n < node->ChildCount; child_n++)
                        DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
                    ImGui::TreePop();
                }
            }
            else
            {
                ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth);
                ImGui::TableNextColumn();
                ImGui::Text("%d", node->Size);
                ImGui::TableNextColumn();
                ImGui::TextUnformatted(node->Type);
            }
        }
    };
    static const MyTreeNode nodes[] =
    {
        { "Root",                         "Folder",       -1,       1, 3    }, // 0
        { "Music",                        "Folder",       -1,       4, 2    }, // 1
        { "Textures",                     "Folder",       -1,       6, 3    }, // 2
        { "desktop.ini",                  "System file",  1024,    -1,-1    }, // 3
        { "File1_a.wav",                  "Audio file",   123000,  -1,-1    }, // 4
        { "File1_b.wav",                  "Audio file",   456000,  -1,-1    }, // 5
        { "Image001.png",                 "Image file",   203128,  -1,-1    }, // 6
        { "Copy of Image001.png",         "Image file",   203256,  -1,-1    }, // 7
        { "Copy of Image001 (Final2).png","Image file",   203512,  -1,-1    }, // 8
    };

    MyTreeNode::DisplayNode(&nodes[0], nodes);

    ImGui::EndTable();
}
ImGui::TreePop();

}
Benefits
Enhanced UI Capability: This feature would allow users to create more complex and organized interfaces, especially useful for applications dealing with hierarchical data.
Consistency with ImGui: Users familiar with ImGui's functionality would have an easier transition and more powerful tools at their disposal.
Implementation Notes
The feature should support various table flags such as resizable columns, row backgrounds, and customizable column widths.
It should allow the nesting of tree nodes within table cells, maintaining the hierarchical structure.
Thank you for considering this feature request. Implementing this would greatly enhance the versatility and usability of DearPyGui for many developers.

@Umbrella167 Umbrella167 added the state: pending not addressed yet label Aug 20, 2024
@v-ein
Copy link
Contributor

v-ein commented Aug 20, 2024

Hello @Umbrella167 - have you tried to create such a layout using existing DPG widgets? If it doesn't work for you, could you please describe what goes wrong?

@Umbrella167
Copy link
Author

1
2

@v-ein
Thank you for your response.

These two images illustrate the functionality I am trying to achieve. Specifically, it can be found in:

dpg.show_imgui_demo() --> Tables & Columns --> Tree view

I attempted to nest a tree node within a table; however, I was unable to achieve the desired effect.

Could you please advise on how to accomplish this using the existing DPG widgets, or if there's another approach I should consider?

Thank you for your assistance.

@v-ein
Copy link
Contributor

v-ein commented Aug 21, 2024

Well, I must admit that it's not trivial but here is a workaround.

The problem with existing widgets is that DPG implements table rows as containers, whereas in ImGui it all works a bit differently... In DPG, you can't put a tree structure in different table rows because that would mean overlapping containers in the code (a tree node is a container, and a table row is a container too). So the workaround simulates the tree by explicitly showing and hiding the rows whenever a node is expanded or collapsed.

One more problem is that in the current version, the toggled_open handler does not fire when the tree node is collapsed, and there's no easy way to detect the collapse event. Therefore I used a selectable to handle this stuff. As a bonus, it highlights the entire row and captures mouse clicks on the entire row (whereas tree_node would only work within a single column). tree_node is still used to render the node the same way a regular tree node (not inserted into a table) would look.

#!/usr/local/bin/python3

from contextlib import contextmanager
from typing import Generator, Union
import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.setup_dearpygui()
dpg.create_viewport(title="Test", width=500, height=300)


INDENT_STEP = 14    # actually depends on font size


def on_row_clicked(sender, value, user_data):
    # Make sure it happens quickly and without flickering
    with dpg.mutex():
        # We don't want to highlight the selectable as "selected"
        dpg.set_value(sender, False)

        table, row = user_data
        root_level, node = dpg.get_item_user_data(row)

        # First of all let's toggle the node's "expanded" status
        is_expanded = not dpg.get_value(node)
        dpg.set_value(node, is_expanded)
        # All children *beyond* this level (but not on this level) will be hidden
        hide_level = 10000 if is_expanded else root_level

        # Now manage the visibility of all the children as necessary
        rows = dpg.get_item_children(table, slot=1)
        root_idx = rows.index(row)
        # We don't want to look at rows preceding our current "root" node
        rows = rows[root_idx + 1:]
        for child_row in rows:
            child_level, child_node = dpg.get_item_user_data(child_row)
            if child_level <= root_level:
                break

            if child_level > hide_level:
                dpg.hide_item(child_row)
            else:
                dpg.show_item(child_row)
                hide_level = 10000 if dpg.get_value(child_node) else child_level


@contextmanager
def table_tree_node(*cells: str, leaf: bool = False) -> Generator[Union[int, str] , None, None]:
    table = dpg.top_container_stack()
    cur_level = dpg.get_item_user_data(table) or 0

    node = dpg.generate_uuid()

    with dpg.table_row(user_data=(cur_level, node)) as row:
        with dpg.group(horizontal=True, horizontal_spacing=0):
            dpg.add_selectable(span_columns=True, callback=on_row_clicked, user_data=(table, row))
            dpg.add_tree_node(
                    tag=node,
                    label=cells[0],
                    indent=cur_level*INDENT_STEP,
                    leaf=leaf,
                    bullet=leaf,
                    default_open=True)

        for label in cells[1:]:
            dpg.add_text(label)

    try:
        dpg.set_item_user_data(table, cur_level + 1)
        yield node
    finally:
        dpg.set_item_user_data(table, cur_level)


def add_table_tree_leaf(*cells: str) -> Union[int, str]:
    with table_tree_node(*cells, leaf=True) as node:
        pass
    return node


with dpg.window():
    dpg.set_primary_window(dpg.last_item(), True)

    # We need to adjust padding so that widgets of different types are positioned properly
    with dpg.theme() as table_theme:
        with dpg.theme_component(dpg.mvAll):
            # Frame padding affects vertical positioning of add_text items within the table
            dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 0)

    with dpg.table(
            borders_outerV=True,
            borders_outerH=True,
            row_background=True,
            policy=dpg.mvTable_SizingFixedFit):

        dpg.bind_item_theme(dpg.last_item(), table_theme)

        dpg.add_table_column(label="Name", width_stretch=True)
        dpg.add_table_column(label="Size")
        dpg.add_table_column(label="Type")

        with table_tree_node("Root", "--", "Folder"):
            with table_tree_node("Music", "--", "Folder"):
                add_table_tree_leaf("File1_a.wav", "123000", "Audio file")
                add_table_tree_leaf("File1_b.wav", "456000", "Audio file")
                with table_tree_node("My favourite", "--", "Folder"):
                    add_table_tree_leaf("Some pop.wav", "456000", "Audio file")
            with table_tree_node("Textures", "--", "Folder"):
                add_table_tree_leaf("Image001.png", "203128", "Image file")
                add_table_tree_leaf("Copy of Image001.png", "203256", "Image file")
                add_table_tree_leaf("Copy of Image001 (Final2).png", "203512", "Image file")
            add_table_tree_leaf("desktop.ini", "1024", "System file")


dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

@Umbrella167
Copy link
Author

@v-ein Thank you so much for your detailed explanation and the workaround you provided! Your method is ingenious and exactly what I needed. This was a huge help to me. Thanks again for your patience and assistance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state: pending not addressed yet
Projects
None yet
Development

No branches or pull requests

2 participants