From e2ade74f8f384584007744c03955597cc0f96768 Mon Sep 17 00:00:00 2001 From: prewettg Date: Tue, 21 Jul 2020 12:34:36 -0700 Subject: [PATCH] Added gui::TreeView widget (#2081) --- cpp/open3d/Open3D.h.in | 1 + cpp/open3d/visualization/gui/Application.cpp | 7 +- cpp/open3d/visualization/gui/ListView.cpp | 29 +- cpp/open3d/visualization/gui/ProgressBar.cpp | 6 +- cpp/open3d/visualization/gui/Theme.h | 7 + cpp/open3d/visualization/gui/TreeView.cpp | 291 +++++++++++++++++++ cpp/open3d/visualization/gui/TreeView.h | 85 ++++++ cpp/open3d/visualization/gui/Util.cpp | 10 +- cpp/open3d/visualization/gui/Util.h | 3 +- cpp/open3d/visualization/gui/Window.cpp | 1 + cpp/pybind/visualization/gui/gui.cpp | 59 +++- examples/python/GUI/all-widgets.py | 20 +- 12 files changed, 499 insertions(+), 20 deletions(-) create mode 100644 cpp/open3d/visualization/gui/TreeView.cpp create mode 100644 cpp/open3d/visualization/gui/TreeView.h diff --git a/cpp/open3d/Open3D.h.in b/cpp/open3d/Open3D.h.in index fbee4412072..eadba97363e 100644 --- a/cpp/open3d/Open3D.h.in +++ b/cpp/open3d/Open3D.h.in @@ -83,6 +83,7 @@ #include "open3d/visualization/gui/TabControl.h" #include "open3d/visualization/gui/TextEdit.h" #include "open3d/visualization/gui/Theme.h" +#include "open3d/visualization/gui/TreeView.h" #include "open3d/visualization/gui/Window.h" #include "open3d/visualization/utility/DrawGeometry.h" #include "open3d/visualization/utility/SelectionPolygon.h" diff --git a/cpp/open3d/visualization/gui/Application.cpp b/cpp/open3d/visualization/gui/Application.cpp index dfae2671b32..9aba023a325 100644 --- a/cpp/open3d/visualization/gui/Application.cpp +++ b/cpp/open3d/visualization/gui/Application.cpp @@ -197,7 +197,12 @@ Application::Application() : impl_(new Application::Impl()) { impl_->theme_.combobox_hover_color = Color(0.5, 0.5, 0.5); impl_->theme_.combobox_arrow_background_color = highlight_color; impl_->theme_.slider_grab_color = Color(0.666, 0.666, 0.666); - impl_->theme_.text_edit_background_color = Color(0.25, 0.25, 0.25); + impl_->theme_.text_edit_background_color = Color(0.1, 0.1, 0.1); + impl_->theme_.list_background_color = Color(0.1, 0.1, 0.1); + impl_->theme_.list_hover_color = Color(0.6, 0.6, 0.6); + impl_->theme_.list_selected_color = Color(0.5, 0.5, 0.5); + impl_->theme_.tree_background_color = impl_->theme_.list_background_color; + impl_->theme_.tree_selected_color = impl_->theme_.list_selected_color; impl_->theme_.tab_inactive_color = impl_->theme_.button_color; impl_->theme_.tab_hover_color = impl_->theme_.button_hover_color; impl_->theme_.tab_active_color = impl_->theme_.button_active_color; diff --git a/cpp/open3d/visualization/gui/ListView.cpp b/cpp/open3d/visualization/gui/ListView.cpp index ea420e56467..7d59a521808 100644 --- a/cpp/open3d/visualization/gui/ListView.cpp +++ b/cpp/open3d/visualization/gui/ListView.cpp @@ -32,6 +32,7 @@ #include #include "open3d/visualization/gui/Theme.h" +#include "open3d/visualization/gui/Util.h" namespace open3d { namespace visualization { @@ -102,6 +103,15 @@ Widget::DrawResult ListView::Draw(const DrawContext &context) { ImVec2(frame.x - context.uiOffsetX, frame.y - context.uiOffsetY)); ImGui::PushItemWidth(frame.width); + ImGui::PushStyleColor(ImGuiCol_FrameBg, + colorToImgui(context.theme.list_background_color)); + ImGui::PushStyleColor(ImGuiCol_Header, // selection color + colorToImgui(context.theme.list_selected_color)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, // hover color + colorToImgui(Color(0, 0, 0, 0))); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, // click-hold color + colorToImgui(context.theme.list_selected_color)); + int height_in_items = int(std::floor(frame.height / ImGui::GetFrameHeight())); @@ -113,6 +123,20 @@ Widget::DrawResult ListView::Draw(const DrawContext &context) { height_in_items)) { for (size_t i = 0; i < impl_->items_.size(); ++i) { bool is_selected = (int(i) == impl_->selected_index_); + // ImGUI's list wants to hover over items, which is not done by + // any major OS, is pretty unnecessary (you can see the cursor + // right over the row), and acts really weird. Worse, the hover + // is drawn instead of the selection color. So to get rid of it + // we need hover to be the selected color iff this item is + // selected, otherwise we want it to be transparent. + if (is_selected) { + ImGui::PushStyleColor( + ImGuiCol_HeaderHovered, + colorToImgui(context.theme.list_selected_color)); + } else { + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, + colorToImgui(Color(0, 0, 0, 0))); + } if (ImGui::Selectable(impl_->items_[i].c_str(), &is_selected, ImGuiSelectableFlags_AllowDoubleClick)) { if (is_selected) { @@ -125,6 +149,7 @@ Widget::DrawResult ListView::Draw(const DrawContext &context) { is_double_click = true; } } + ImGui::PopStyleColor(); } ImGui::ListBoxFooter(); @@ -132,12 +157,14 @@ Widget::DrawResult ListView::Draw(const DrawContext &context) { impl_->selected_index_ = new_selected_idx; if (impl_->on_value_changed_) { impl_->on_value_changed_(GetSelectedValue(), is_double_click); - result = Widget::DrawResult::REDRAW; } + result = Widget::DrawResult::REDRAW; } } DrawImGuiPopEnabledState(); + ImGui::PopStyleColor(4); + ImGui::PopItemWidth(); return result; } diff --git a/cpp/open3d/visualization/gui/ProgressBar.cpp b/cpp/open3d/visualization/gui/ProgressBar.cpp index 258274887fb..480c9a7aaf2 100644 --- a/cpp/open3d/visualization/gui/ProgressBar.cpp +++ b/cpp/open3d/visualization/gui/ProgressBar.cpp @@ -30,6 +30,7 @@ #include #include "open3d/visualization/gui/Theme.h" +#include "open3d/visualization/gui/Util.h" namespace open3d { namespace visualization { @@ -55,10 +56,7 @@ Size ProgressBar::CalcPreferredSize(const Theme& theme) const { Widget::DrawResult ProgressBar::Draw(const DrawContext& context) { auto& frame = GetFrame(); auto fg = context.theme.border_color; - auto color = IM_COL32(int(std::round(255.0f * fg.GetRed())), - int(std::round(255.0f * fg.GetGreen())), - int(std::round(255.0f * fg.GetBlue())), - int(std::round(255.0f * fg.GetAlpha()))); + auto color = colorToImguiRGBA(fg); float rounding = frame.height / 2.0f; ImGui::GetWindowDrawList()->AddRect( ImVec2(frame.x, frame.y), diff --git a/cpp/open3d/visualization/gui/Theme.h b/cpp/open3d/visualization/gui/Theme.h index 2f8042a634c..faef64a8d85 100644 --- a/cpp/open3d/visualization/gui/Theme.h +++ b/cpp/open3d/visualization/gui/Theme.h @@ -73,6 +73,13 @@ struct Theme { Color text_edit_background_color; + Color list_background_color; + Color list_hover_color; + Color list_selected_color; + + Color tree_background_color; + Color tree_selected_color; + Color tab_inactive_color; Color tab_hover_color; Color tab_active_color; diff --git a/cpp/open3d/visualization/gui/TreeView.cpp b/cpp/open3d/visualization/gui/TreeView.cpp new file mode 100644 index 00000000000..3f2c90d29a6 --- /dev/null +++ b/cpp/open3d/visualization/gui/TreeView.cpp @@ -0,0 +1,291 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// The MIT License (MIT) +// +// Copyright (c) 2020 www.open3d.org +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// ---------------------------------------------------------------------------- + +#include "open3d/visualization/gui/TreeView.h" + +#include +#include +#include +#include + +#include "open3d/visualization/gui/Theme.h" +#include "open3d/visualization/gui/Util.h" + +namespace open3d { +namespace visualization { +namespace gui { + +namespace { +static int g_treeview_id = 1; +} + +struct TreeView::Impl { + static TreeView::ItemId g_next_id; + + // Note: use std::list because pointers remain valid, unlike std::vector + // which will invalidate pointers when it resizes the underlying + // array + struct Item { + TreeView::ItemId id = -1; + std::string id_string; + std::string text; + Item *parent = nullptr; + std::list children; + }; + int id_; + Item root_; + std::unordered_map id2item_; + TreeView::ItemId selected_id_ = -1; + bool can_select_parents_ = false; + std::function on_selection_changed_; +}; + +TreeView::ItemId TreeView::Impl::g_next_id = 0; + +TreeView::TreeView() : impl_(new TreeView::Impl()) { + impl_->id_ = g_treeview_id++; + impl_->root_.id = Impl::g_next_id++; + impl_->id2item_[impl_->root_.id] = &impl_->root_; +} + +TreeView::~TreeView() {} + +TreeView::ItemId TreeView::GetRootItem() const { return impl_->root_.id; } + +TreeView::ItemId TreeView::AddItem(ItemId parent_id, const char *text) { + Impl::Item item; + item.id = Impl::g_next_id++; + // ImGUI uses the text to identify the item, create a ID string + std::stringstream s; + s << "treeview" << impl_->id_ << "item" << item.id; + item.id_string = s.str(); + item.text = text; + + Impl::Item *parent = &impl_->root_; + auto parent_it = impl_->id2item_.find(parent_id); + if (parent_it != impl_->id2item_.end()) { + parent = parent_it->second; + } + item.parent = parent; + parent->children.push_back(item); + impl_->id2item_[item.id] = &parent->children.back(); + + return item.id; +} + +void TreeView::RemoveItem(ItemId item_id) { + auto item_it = impl_->id2item_.find(item_id); + if (item_it != impl_->id2item_.end()) { + auto *item = item_it->second; + // Erase the item here, because RemoveItem(child) will also erase, + // which will invalidate our iterator. + impl_->id2item_.erase(item_it); + + // Remove children. Note that we can't use a foreach loop here, + // because when we remove the item from its parent it will + // invalidate the iterator to the current item that exists under + // the hood, making `it++` not workable. So we use a while loop + // instead. Because this is a list, we can erase from the front + // in O(1). + while (!item->children.empty()) { + RemoveItem(item->children.front().id); + } + + // Remove ourself from our parent's list of children + if (item->parent) { + for (auto sibling = item->parent->children.begin(); + sibling != item->parent->children.end(); ++sibling) { + if (sibling->id == item_id) { + item->parent->children.erase(sibling); + break; + } + } + } + } +} + +const char *TreeView::GetItemText(ItemId item_id) const { + auto item_it = impl_->id2item_.find(item_id); + if (item_it != impl_->id2item_.end()) { + return item_it->second->text.c_str(); + } + return nullptr; +} + +void TreeView::SetItemText(ItemId item_id, const char *text) { + auto item_it = impl_->id2item_.find(item_id); + if (item_it != impl_->id2item_.end()) { + item_it->second->text = text; + } +} + +std::vector TreeView::GetItemChildren( + ItemId parent_id) const { + std::vector children; + auto item_it = impl_->id2item_.find(parent_id); + if (item_it != impl_->id2item_.end()) { + auto *parent = item_it->second->parent; + if (parent) { + children.reserve(parent->children.size()); + for (auto &child : parent->children) { + children.push_back(child.id); + } + } + } + return children; +} + +bool TreeView::GetCanSelectItemsWithChildren() const { + return impl_->can_select_parents_; +} + +void TreeView::SetCanSelectItemsWithChildren(bool can_select) { + impl_->can_select_parents_ = can_select; +} + +TreeView::ItemId TreeView::GetSelectedItemId() const { + if (impl_->selected_id_ < 0) { + return impl_->root_.id; + } else { + return impl_->selected_id_; + } +} + +void TreeView::SetSelectedItemId(ItemId item_id) { + impl_->selected_id_ = item_id; +} + +void TreeView::SetOnSelectionChanged( + std::function on_selection_changed) { + impl_->on_selection_changed_ = on_selection_changed; +} + +Size TreeView::CalcPreferredSize(const Theme &theme) const { + return Size(Widget::DIM_GROW, Widget::DIM_GROW); +} + +Widget::DrawResult TreeView::Draw(const DrawContext &context) { + auto &frame = GetFrame(); + + DrawImGuiPushEnabledState(); + ImGui::SetCursorPosX(frame.x - context.uiOffsetX); + ImGui::SetCursorPosY(frame.y - context.uiOffsetY); + + // ImGUI's tree wants to highlight the row as the user moves over it. + // There are several problems here. First, there seems to be a bug in + // ImGUI where the highlight ignores the pushed item width and extends + // to the end of the ImGUI-window (i.e. the topmost parent Widget). This + // means the highlight extends into any margins we have. Not good. Second, + // the highlight extends past the clickable area, which is misleading. + // Third, no operating system has hover highlights like this, and it looks + // really strange. I mean, you can see the cursor right over your text, + // what do you need a highligh for? So make this highlight transparent. + ImGui::PushStyleColor(ImGuiCol_HeaderActive, // click-hold on item + colorToImgui(Color(0, 0, 0, 0))); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, + colorToImgui(Color(0, 0, 0, 0))); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, + colorToImgui(context.theme.tree_background_color)); + + // ImGUI's tree is basically a layout in the parent ImGUI window. + // Make this a child so it's all in a nice frame. + ImGui::BeginChild(impl_->id_, ImVec2(frame.width, frame.height), true); + + Impl::Item *new_selection = nullptr; + + std::function DrawItem; + DrawItem = [&DrawItem, this, &frame, &context, + &new_selection](Impl::Item &item) { + // ImGUI's tree doesn't seem to support selected items, + // so we have to draw our own selection. + if (item.id == impl_->selected_id_) { + auto h = ImGui::GetTextLineHeightWithSpacing(); + // Since we are in a child, the cursor is relative to the upper left + // of the tree's frame. To draw directly to the window list we + // need to the absolute coordinates (relative the OS window's + // upper left) + auto y = frame.y + ImGui::GetCursorPosY() - ImGui::GetScrollY(); + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(frame.x, y), ImVec2(frame.GetRight(), y + h), + colorToImguiRGBA(context.theme.tree_selected_color)); + } + + int flags = ImGuiTreeNodeFlags_DefaultOpen; + if (impl_->can_select_parents_) { + flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick; + flags |= ImGuiTreeNodeFlags_OpenOnArrow; + } + if (item.children.empty()) { + flags |= ImGuiTreeNodeFlags_Leaf; + } + bool is_selectable = + (item.children.empty() || impl_->can_select_parents_); + if (ImGui::TreeNodeEx(item.id_string.c_str(), flags, "%s", + item.text.c_str())) { + if (ImGui::IsItemClicked() && is_selectable) { + impl_->selected_id_ = item.id; + new_selection = &item; + } + for (auto &child : item.children) { + DrawItem(child); + } + ImGui::TreePop(); + } else { + if (ImGui::IsItemClicked() && is_selectable) { + impl_->selected_id_ = item.id; + new_selection = &item; + } + } + }; + for (auto &top : impl_->root_.children) { + DrawItem(top); + } + + ImGui::EndChild(); + + ImGui::PopStyleColor(3); + DrawImGuiPopEnabledState(); + + // If the selection changed, handle the callback here, after we have + // finished drawing, so that the callback is able to change the contents + // of the tree if it wishes (which could cause a crash if done while + // drawing, e.g. deleting the current item). + auto result = Widget::DrawResult::NONE; + if (new_selection) { + if (impl_->on_selection_changed_) { + impl_->on_selection_changed_(new_selection->text.c_str(), + new_selection->id); + } + result = Widget::DrawResult::REDRAW; + } + + return result; +} + +} // namespace gui +} // namespace visualization +} // namespace open3d diff --git a/cpp/open3d/visualization/gui/TreeView.h b/cpp/open3d/visualization/gui/TreeView.h new file mode 100644 index 00000000000..676c77413b5 --- /dev/null +++ b/cpp/open3d/visualization/gui/TreeView.h @@ -0,0 +1,85 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// The MIT License (MIT) +// +// Copyright (c) 2020 www.open3d.org +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +#include "open3d/visualization/gui/Widget.h" + +namespace open3d { +namespace visualization { +namespace gui { + +class TreeView : public Widget { + using Super = Widget; + +public: + using ItemId = int; + + TreeView(); + ~TreeView(); + + /// Returns the ID of the root item, that is, + /// AddItem(GetRootItem(), "...") will be a top-level item. + ItemId GetRootItem() const; + /// Adds an item to the tree. + ItemId AddItem(ItemId parent_id, const char* text); + /// Removes an item an all its children (if any) from the tree + void RemoveItem(ItemId item_id); + /// Returns the text of the item, or nullptr if item_id cannot be found. + const char* GetItemText(ItemId item_id) const; + void SetItemText(ItemId item_id, const char* text); + std::vector GetItemChildren(ItemId parent_id) const; + + bool GetCanSelectItemsWithChildren() const; + /// If true, enables selecting items that have children. + /// Items can be toggled open/closed with the triangles or by + /// double-clicking. Default is false. + void SetCanSelectItemsWithChildren(bool can_select); + + /// Returns the currently selected item id in the tree. + ItemId GetSelectedItemId() const; + /// Selects the indicated item of the list. Does not call onValueChanged. + void SetSelectedItemId(ItemId item_id); + + Size CalcPreferredSize(const Theme& theme) const override; + + DrawResult Draw(const DrawContext& context) override; + + /// Calls onSelectionChanged(const char *sel_text, ItemId sel_item_id) + /// when the list selection changes because of user action. + void SetOnSelectionChanged( + std::function on_selection_changed); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace gui +} // namespace visualization +} // namespace open3d diff --git a/cpp/open3d/visualization/gui/Util.cpp b/cpp/open3d/visualization/gui/Util.cpp index ed66d2a84f3..64dc5846aaa 100644 --- a/cpp/open3d/visualization/gui/Util.cpp +++ b/cpp/open3d/visualization/gui/Util.cpp @@ -26,17 +26,25 @@ #include "open3d/visualization/gui/Util.h" +#include #include "open3d/visualization/gui/Color.h" namespace open3d { namespace visualization { namespace gui { -ImVec4 colorToImgui(const Color &color) { +ImVec4 colorToImgui(const Color& color) { return ImVec4(color.GetRed(), color.GetGreen(), color.GetBlue(), color.GetAlpha()); } +uint32_t colorToImguiRGBA(const Color& color) { + return IM_COL32(int(std::round(255.0f * color.GetRed())), + int(std::round(255.0f * color.GetGreen())), + int(std::round(255.0f * color.GetBlue())), + int(std::round(255.0f * color.GetAlpha()))); +} + } // namespace gui } // namespace visualization } // namespace open3d diff --git a/cpp/open3d/visualization/gui/Util.h b/cpp/open3d/visualization/gui/Util.h index fee82ff0cfb..cf2bea901ce 100644 --- a/cpp/open3d/visualization/gui/Util.h +++ b/cpp/open3d/visualization/gui/Util.h @@ -37,11 +37,12 @@ namespace gui { class Color; -// This one function is here, because ImVec4 requires imgui.h, and can't be +// These functions are here, because ImVec4 requires imgui.h, and can't be // forward-declared because we need to know the size, since it is a return // value. Since imgui.h is an implementation detail, we can't put this function // in Color or it would pull in imgui.h pretty much everywhere that gui is used. ImVec4 colorToImgui(const Color& color); +uint32_t colorToImguiRGBA(const Color& color); } // namespace gui } // namespace visualization diff --git a/cpp/open3d/visualization/gui/Window.cpp b/cpp/open3d/visualization/gui/Window.cpp index e57d1b76908..28517e4bb97 100644 --- a/cpp/open3d/visualization/gui/Window.cpp +++ b/cpp/open3d/visualization/gui/Window.cpp @@ -271,6 +271,7 @@ Window::Window(const std::string& title, style.WindowBorderSize = 0; style.FrameBorderSize = theme.border_width; style.FrameRounding = theme.border_radius; + style.ChildRounding = theme.border_radius; style.Colors[ImGuiCol_WindowBg] = colorToImgui(theme.background_color); style.Colors[ImGuiCol_Text] = colorToImgui(theme.text_color); style.Colors[ImGuiCol_Border] = colorToImgui(theme.border_color); diff --git a/cpp/pybind/visualization/gui/gui.cpp b/cpp/pybind/visualization/gui/gui.cpp index 75d4ab5f8b3..9ecdb1f10c1 100644 --- a/cpp/pybind/visualization/gui/gui.cpp +++ b/cpp/pybind/visualization/gui/gui.cpp @@ -50,6 +50,7 @@ #include "open3d/visualization/gui/TabControl.h" #include "open3d/visualization/gui/TextEdit.h" #include "open3d/visualization/gui/Theme.h" +#include "open3d/visualization/gui/TreeView.h" #include "open3d/visualization/gui/VectorEdit.h" #include "open3d/visualization/gui/Widget.h" #include "open3d/visualization/gui/Window.h" @@ -458,8 +459,8 @@ void pybind_gui_classes(py::module &m) { "The index of the currently selected item") .def("set_on_selection_changed", &Combobox::SetOnValueChanged, "Calls f(str, int) when user selects item from combobox. " - "Arguments " - "are the selected text and selected index, respectively"); + "Arguments are the selected text and selected index, " + "respectively"); // ---- ImageLabel ---- py::class_, Widget> imagelabel( @@ -539,8 +540,7 @@ void pybind_gui_classes(py::module &m) { numedit.def(py::init(), "Creates a NumberEdit that is either integers (INT) or " "floating point (DOUBLE). The initial value is 0 and the " - "limits " - "are +/- max integer (roughly).") + "limits are +/- max integer (roughly).") .def("__repr__", [](const NumberEdit &ne) { auto val = ne.GetDoubleValue(); @@ -573,8 +573,7 @@ void pybind_gui_classes(py::module &m) { "Sets the minimum and maximum values for the number") .def("set_on_value_changed", &NumberEdit::SetOnValueChanged, "Sets f(new_value) which is called with a Float when user " - "changes " - "widget's value"); + "changes widget's value"); // ---- ProgressBar---- py::class_, Widget> progress( @@ -654,8 +653,7 @@ void pybind_gui_classes(py::module &m) { "Sets the minimum and maximum values for the slider") .def("set_on_value_changed", &Slider::SetOnValueChanged, "Sets f(new_value) which is called with a Float when user " - "changes " - "widget's value"); + "changes widget's value"); // ---- TabControl ---- py::class_, Widget> tabctrl( @@ -689,12 +687,51 @@ void pybind_gui_classes(py::module &m) { "The placeholder text displayed when text value is empty") .def("set_on_text_changed", &TextEdit::SetOnTextChanged, "Sets f(new_text) which is called whenever the the user makes " - "a " - "change to the text") + "a change to the text") .def("set_on_value_changed", &TextEdit::SetOnValueChanged, "Sets f(new_text) which is called with the new text when the " + "user completes text editing"); + + // ---- TreeView ---- + py::class_, Widget> treeview( + m, "TreeView", "Hierarchical list"); + treeview.def(py::init<>(), "Creates an empty TreeView widget") + .def("__repr__", + [](const TreeView &tv) { + std::stringstream s; + s << "TreeView (" << tv.GetFrame().x << ", " + << tv.GetFrame().y << "), " << tv.GetFrame().width + << " x " << tv.GetFrame().height; + return s.str().c_str(); + }) + .def("get_root_item", &TreeView::GetRootItem, + "Returns the root item. This item is invisible, so its child " + "are " + "the top-level items") + .def("add_item", &TreeView::AddItem, + "Adds a child item to the parent. add_item(parent, text)") + .def("remove_item", &TreeView::RemoveItem, + "Removes an item and all its children (if any)") + .def("get_item_text", &TreeView::GetItemText, + "Returns the text of the item") + .def("set_item_text", &TreeView::SetItemText, + "Sets the text of an item") + .def_property( + "can_select_items_with_children", + &TreeView::GetCanSelectItemsWithChildren, + &TreeView::SetCanSelectItemsWithChildren, + "If set to False, clicking anywhere on an item with " + "will toggle the item open or closed; the item cannot be " + "selected. If set to True, items with children can be " + "selected, and to toggle open/closed requires clicking " + "the arrow or double-clicking the item") + .def_property("selected_item", &TreeView::GetSelectedItemId, + &TreeView::SetSelectedItemId, + "The currently selected item") + .def("set_on_selection_changed", &TreeView::SetOnSelectionChanged, + "Sets f(new_item_text, new_item_id) which is called when the " "user " - "completes text editing"); + "changes the selection."); // ---- VectorEdit ---- py::class_, Widget> vectoredit( diff --git a/examples/python/GUI/all-widgets.py b/examples/python/GUI/all-widgets.py index d73f3ac1e9b..9926a954063 100644 --- a/examples/python/GUI/all-widgets.py +++ b/examples/python/GUI/all-widgets.py @@ -113,13 +113,28 @@ def __init__(self): logo = gui.ImageLabel(basedir + "/icon-32.png") collapse.add_child(logo) - # Add a list of items. + # Add a list of items lv = gui.ListView() lv.set_items(["Ground", "Trees", "Buildings" "Cars", "People"]) lv.selected_index = lv.selected_index + 2 # initially is -1, so now 1 lv.set_on_selection_changed(self._on_list) collapse.add_child(lv) + # Add a tree view + tree = gui.TreeView() + tree.add_item(tree.get_root_item(), "Camera") + geo_id = tree.add_item(tree.get_root_item(), "Geometries") + mesh_id = tree.add_item(geo_id, "Mesh") + tree.add_item(mesh_id, "Triangles") + tree.add_item(mesh_id, "Albedo texture") + tree.add_item(mesh_id, "Normal map") + points_id = tree.add_item(geo_id, "Points") + tree.can_select_items_with_children = True + tree.set_on_selection_changed(self._on_tree) + # does not call on_selection_changed: user did not change selection + tree.selected_item = points_id + collapse.add_child(tree) + # Add two number editors, one for integers and one for floating point # Number editor can clamp numbers to a range, although this is more # useful for integers than for floating point. @@ -289,6 +304,9 @@ def _on_combo(self, new_val, new_idx): def _on_list(self, new_val, is_dbl_click): print(new_val) + def _on_tree(self, new_text, new_item): + print(new_text) + def _on_slider(self, new_val): self._progress.value = new_val / 20.0