diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a9051c1ba..d57da82bb14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,7 +83,7 @@ - Fix color artifacts in PointCloud projection due to CUDA race condition [(PR #7424)](https://github.com/isl-org/Open3D/pull/7424) - Fix Windows build failure for PyTorch ops due to PyTorch's bundled fmt (v11+) requiring `/utf-8` with MSVC (PR #7447) - Add `GetMenu` for MenuBase for easy menu item/submenu control. (PR #7295) - +- Add expand/collapse state support for TreeView items in GUI (PR #7451) ## 0.13 - CUDA support 10.1 -> 11.0. Tensorflow 2.3.1 -> 2.4.1. PyTorch 1.6.0 -> 1.7.1 (PR #3049). This requires a custom PyTorch wheel from due to PyTorch issue #52663 diff --git a/cpp/open3d/visualization/gui/TreeView.cpp b/cpp/open3d/visualization/gui/TreeView.cpp index 72fca10bd0e..1edb21e6664 100644 --- a/cpp/open3d/visualization/gui/TreeView.cpp +++ b/cpp/open3d/visualization/gui/TreeView.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -212,6 +213,7 @@ struct TreeView::Impl { std::shared_ptr cell; Item *parent = nullptr; std::list children; + std::optional expanded; }; int id_; Item root_; @@ -361,6 +363,22 @@ void TreeView::Layout(const LayoutContext &context) { // to defer layout to Draw(). } +bool TreeView::IsItemExpanded(ItemId item_id) const { + auto it = impl_->id2item_.find(item_id); + if (it == impl_->id2item_.end()) { + return false; + } + return it->second->expanded.value_or(true); +} + +void TreeView::SetItemExpanded(ItemId item_id, bool expanded) { + auto it = impl_->id2item_.find(item_id); + if (it == impl_->id2item_.end()) { + return; + } + it->second->expanded = expanded; +} + Widget::DrawResult TreeView::Draw(const DrawContext &context) { auto result = Widget::DrawResult::NONE; auto &frame = GetFrame(); @@ -417,8 +435,8 @@ Widget::DrawResult TreeView::Draw(const DrawContext &context) { colorToImguiRGBA(context.theme.tree_selected_color)); } - int flags = ImGuiTreeNodeFlags_DefaultOpen | - ImGuiTreeNodeFlags_AllowItemOverlap; + int flags = ImGuiTreeNodeFlags_AllowItemOverlap; + if (impl_->can_select_parents_) { flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick; flags |= ImGuiTreeNodeFlags_OpenOnArrow; @@ -465,7 +483,12 @@ Widget::DrawResult TreeView::Draw(const DrawContext &context) { } }; - if (ImGui::TreeNodeEx(item.id_string.c_str(), flags, "%s", "")) { + ImGui::SetNextItemOpen(item.expanded.value_or(true)); + + bool open = ImGui::TreeNodeEx(item.id_string.c_str(), flags, "%s", ""); + item.expanded = open; + + if (open) { DrawThis(item, height, is_selectable); for (auto &child : item.children) { diff --git a/cpp/open3d/visualization/gui/TreeView.h b/cpp/open3d/visualization/gui/TreeView.h index 956f896149e..d19527492e7 100644 --- a/cpp/open3d/visualization/gui/TreeView.h +++ b/cpp/open3d/visualization/gui/TreeView.h @@ -134,6 +134,9 @@ class TreeView : public Widget { void SetOnSelectionChanged( std::function on_selection_changed); + bool IsItemExpanded(ItemId item_id) const; + void SetItemExpanded(ItemId item_id, bool expanded); + private: struct Impl; std::unique_ptr impl_; diff --git a/cpp/pybind/visualization/gui/gui.cpp b/cpp/pybind/visualization/gui/gui.cpp index 2d92297062f..e3f5d0c331b 100644 --- a/cpp/pybind/visualization/gui/gui.cpp +++ b/cpp/pybind/visualization/gui/gui.cpp @@ -1664,7 +1664,9 @@ void pybind_gui_definitions(py::module &m) { "The currently selected item") .def("set_on_selection_changed", &TreeView::SetOnSelectionChanged, "Sets f(new_item_id) which is called when the user " - "changes the selection."); + "changes the selection.") + .def("is_item_expanded", &TreeView::IsItemExpanded) + .def("set_item_expanded", &TreeView::SetItemExpanded); // ---- TreeView cells ---- auto checkable_cell = static_cast< diff --git a/cpp/tests/visualization/CMakeLists.txt b/cpp/tests/visualization/CMakeLists.txt index 1d216c0591f..98877c553e7 100644 --- a/cpp/tests/visualization/CMakeLists.txt +++ b/cpp/tests/visualization/CMakeLists.txt @@ -2,4 +2,17 @@ if (BUILD_GUI) target_sources(tests PRIVATE rendering/MaterialModifier.cpp ) + + add_executable(TreeViewTest + TreeViewTest.cpp + ) + + target_link_libraries(TreeViewTest + PRIVATE + Open3D::Open3D + ) + + set_target_properties(TreeViewTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin + ) endif() diff --git a/cpp/tests/visualization/TreeViewTest.cpp b/cpp/tests/visualization/TreeViewTest.cpp new file mode 100644 index 00000000000..88d6afa28b3 --- /dev/null +++ b/cpp/tests/visualization/TreeViewTest.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "open3d/visualization/gui/Application.h" +#include "open3d/visualization/gui/Window.h" +#include "open3d/visualization/gui/TreeView.h" +#include "open3d/visualization/gui/Layout.h" + +using namespace open3d::visualization::gui; + +int main() { + auto& app = Application::GetInstance(); + app.Initialize(); + + auto window = std::make_shared("TreeView GUI Test", 400, 300); + + // Layout + auto layout = std::make_shared(10); // spacing = 10 + + // TreeView + auto tree = std::make_shared(); + auto root = tree->GetRootItem(); + tree->AddTextItem(root, "Item A"); + tree->AddTextItem(root, "Item B"); + + layout->AddChild(tree); + window->AddChild(layout); + + app.AddWindow(window); + + // Close automatically after 2 seconds + app.PostToMainThread(nullptr, []() { + // NOTE: + // This is a GUI smoke test. + // The window is shown briefly to ensure TreeView can be + // created, attached, and rendered without crashing. + // The application exits automatically to avoid blocking CI. + + std::this_thread::sleep_for(std::chrono::seconds(2)); + Application::GetInstance().Quit(); + }); + + app.Run(); + return 0; +}