Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/include/UI_tree_view.hh
| Show All 11 Lines | |||||
| * You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
| * along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| */ | */ | ||||
| /** \file | /** \file | ||||
| * \ingroup editorui | * \ingroup editorui | ||||
| * | * | ||||
| * API for simple creation of tree UIs supporting advanced features. | * API for simple creation of tree UIs supporting typically needed features. | ||||
| * https://wiki.blender.org/wiki/Source/Interface/Views | * https://wiki.blender.org/wiki/Source/Interface/Views/Tree_Views | ||||
| */ | */ | ||||
| #pragma once | #pragma once | ||||
| #include <array> | #include <array> | ||||
| #include <functional> | #include <functional> | ||||
| #include <memory> | #include <memory> | ||||
| #include <string> | #include <string> | ||||
| #include "DNA_defs.h" | #include "DNA_defs.h" | ||||
| #include "BLI_function_ref.hh" | #include "BLI_function_ref.hh" | ||||
| #include "BLI_vector.hh" | #include "BLI_vector.hh" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| struct bContext; | struct bContext; | ||||
| struct PointerRNA; | |||||
| struct uiBlock; | struct uiBlock; | ||||
| struct uiBut; | struct uiBut; | ||||
| struct uiButTreeRow; | struct uiButTreeRow; | ||||
| struct uiLayout; | struct uiLayout; | ||||
| struct wmEvent; | struct wmEvent; | ||||
| struct wmDrag; | struct wmDrag; | ||||
| namespace blender::ui { | namespace blender::ui { | ||||
| class AbstractTreeView; | class AbstractTreeView; | ||||
| class AbstractTreeViewItem; | class AbstractTreeViewItem; | ||||
| class AbstractTreeViewItemDropController; | class AbstractTreeViewItemDropController; | ||||
| class AbstractTreeViewItemDragController; | class AbstractTreeViewItemDragController; | ||||
| /* ---------------------------------------------------------------------- */ | /* ---------------------------------------------------------------------- */ | ||||
| /** \name Tree-View Item Container | /** \name Tree-View Item Container | ||||
| * | |||||
| * Base class for tree-view and tree-view items, so both can contain children. | |||||
| * \{ */ | * \{ */ | ||||
| /** | /** | ||||
| * Helper base class to expose common child-item data and functionality to both #AbstractTreeView | * Both the tree-view (as the root of the tree) and the items can have children. This is the base | ||||
| * and #AbstractTreeViewItem. | * class for both, to store and manage child items. Children are owned by their parent container | ||||
| * (tree-view or item). | |||||
| * | * | ||||
| * That means this type can be used whenever either a #AbstractTreeView or a | * That means this type can be used whenever either an #AbstractTreeView or an | ||||
| * #AbstractTreeViewItem is needed. | * #AbstractTreeViewItem is needed, but the #TreeViewOrItem alias is a better name to use then. | ||||
| */ | */ | ||||
| class TreeViewItemContainer { | class TreeViewItemContainer { | ||||
| friend class AbstractTreeView; | friend class AbstractTreeView; | ||||
| friend class AbstractTreeViewItem; | friend class AbstractTreeViewItem; | ||||
| /* Private constructor, so only the friends above can create this! */ | /* Private constructor, so only the friends above can create this! */ | ||||
| TreeViewItemContainer() = default; | TreeViewItemContainer() = default; | ||||
| Show All 35 Lines | |||||
| protected: | protected: | ||||
| void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; | void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; | ||||
| }; | }; | ||||
| ENUM_OPERATORS(TreeViewItemContainer::IterOptions, | ENUM_OPERATORS(TreeViewItemContainer::IterOptions, | ||||
| TreeViewItemContainer::IterOptions::SkipCollapsed); | TreeViewItemContainer::IterOptions::SkipCollapsed); | ||||
| /** \} */ | /** The container class is the base for both the tree-view and the items. This alias gives it a | ||||
| * clearer name for handles that accept both. Use whenever something wants to act on child-items, | |||||
| /* ---------------------------------------------------------------------- */ | * irrespective of if they are stored at root level or as children of some other item. */ | ||||
| /** \name Tree-View Builders | using TreeViewOrItem = TreeViewItemContainer; | ||||
| * \{ */ | |||||
| class TreeViewBuilder { | |||||
| uiBlock &block_; | |||||
| public: | |||||
| TreeViewBuilder(uiBlock &block); | |||||
| void build_tree_view(AbstractTreeView &tree_view); | |||||
| }; | |||||
| class TreeViewLayoutBuilder { | |||||
| uiBlock &block_; | |||||
| friend TreeViewBuilder; | |||||
| public: | |||||
| void build_row(AbstractTreeViewItem &item) const; | |||||
| uiBlock &block() const; | |||||
| uiLayout *current_layout() const; | |||||
| private: | |||||
| /* Created through #TreeViewBuilder. */ | |||||
| TreeViewLayoutBuilder(uiBlock &block); | |||||
| static void polish_layout(const uiBlock &block); | |||||
| }; | |||||
| /** \} */ | /** \} */ | ||||
| /* ---------------------------------------------------------------------- */ | /* ---------------------------------------------------------------------- */ | ||||
| /** \name Tree-View Base Class | /** \name Tree-View Base Class | ||||
| * \{ */ | * \{ */ | ||||
| class AbstractTreeView : public TreeViewItemContainer { | class AbstractTreeView : public TreeViewItemContainer { | ||||
| friend AbstractTreeViewItem; | friend class AbstractTreeViewItem; | ||||
| friend TreeViewBuilder; | friend class TreeViewBuilder; | ||||
| /** | /** | ||||
| * Only one item can be renamed at a time. So the tree is informed about the renaming state to | * Only one item can be renamed at a time. So the tree is informed about the renaming state to | ||||
| * enforce that. | * enforce that. | ||||
| */ | */ | ||||
| std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_; | std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_; | ||||
| bool is_reconstructed_ = false; | bool is_reconstructed_ = false; | ||||
| public: | public: | ||||
| virtual ~AbstractTreeView() = default; | virtual ~AbstractTreeView() = default; | ||||
| void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; | void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; | ||||
| /** Only one item can be renamed at a time. */ | /** Only one item can be renamed at a time. */ | ||||
| bool is_renaming() const; | bool is_renaming() const; | ||||
| protected: | |||||
| virtual void build_tree() = 0; | |||||
| /** | /** | ||||
| * Check if the tree is fully (re-)constructed. That means, both #build_tree() and | * Check if the tree is fully (re-)constructed. That means, both #build_tree() and | ||||
| * #update_from_old() have finished. | * #update_from_old() have finished. | ||||
| */ | */ | ||||
| bool is_reconstructed() const; | bool is_reconstructed() const; | ||||
| protected: | |||||
| virtual void build_tree() = 0; | |||||
| private: | private: | ||||
| /** | /** | ||||
| * Match the tree-view against an earlier version of itself (if any) and copy the old UI state | * Match the tree-view against an earlier version of itself (if any) and copy the old UI state | ||||
| * (e.g. collapsed, active, selected, renaming, etc.) to the new one. See | * (e.g. collapsed, active, selected, renaming, etc.) to the new one. See | ||||
| * #AbstractTreeViewItem.update_from_old(). | * #AbstractTreeViewItem.update_from_old(). | ||||
| */ | */ | ||||
| void update_from_old(uiBlock &new_block); | void update_from_old(uiBlock &new_block); | ||||
| static void update_children_from_old_recursive(const TreeViewItemContainer &new_items, | static void update_children_from_old_recursive(const TreeViewOrItem &new_items, | ||||
| const TreeViewItemContainer &old_items); | const TreeViewOrItem &old_items); | ||||
| static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item, | static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item, | ||||
| const TreeViewItemContainer &items); | const TreeViewOrItem &items); | ||||
| /** | /** | ||||
| * Items may want to do additional work when state changes. But these state changes can only be | * Items may want to do additional work when state changes. But these state changes can only be | ||||
| * reliably detected after the tree has completed reconstruction (see #is_reconstructed()). So | * reliably detected after the tree has completed reconstruction (see #is_reconstructed()). So | ||||
| * the actual state changes are done in a delayed manner through this function. | * the actual state changes are done in a delayed manner through this function. | ||||
| */ | */ | ||||
| void change_state_delayed(); | void change_state_delayed(); | ||||
| void build_layout_from_tree(const TreeViewLayoutBuilder &builder); | |||||
| }; | }; | ||||
| /** \} */ | /** \} */ | ||||
| /* ---------------------------------------------------------------------- */ | /* ---------------------------------------------------------------------- */ | ||||
| /** \name Tree-View Item Type | /** \name Tree-View Item Type | ||||
| * \{ */ | * \{ */ | ||||
| /** \brief Abstract base class for defining a customizable tree-view item. | /** \brief Abstract base class for defining a customizable tree-view item. | ||||
| * | * | ||||
| * The tree-view item defines how to build its data into a tree-row. There are implementations for | * The tree-view item defines how to build its data into a tree-row. There are implementations for | ||||
| * common layouts, e.g. #BasicTreeViewItem. | * common layouts, e.g. #BasicTreeViewItem. | ||||
| * It also stores state information that needs to be persistent over redraws, like the collapsed | * It also stores state information that needs to be persistent over redraws, like the collapsed | ||||
| * state. | * state. | ||||
| */ | */ | ||||
| class AbstractTreeViewItem : public TreeViewItemContainer { | class AbstractTreeViewItem : public TreeViewItemContainer { | ||||
| friend class AbstractTreeView; | friend class AbstractTreeView; | ||||
| friend class TreeViewLayoutBuilder; | friend class TreeViewLayoutBuilder; | ||||
| /* Higher-level API. */ | |||||
| friend class TreeViewItemAPIWrapper; | |||||
| public: | |||||
| private: | private: | ||||
| bool is_open_ = false; | bool is_open_ = false; | ||||
| bool is_active_ = false; | bool is_active_ = false; | ||||
| bool is_renaming_ = false; | bool is_renaming_ = false; | ||||
| protected: | protected: | ||||
| /** This label is used for identifying an item (together with its parent's labels). */ | /** This label is used for identifying an item within its parent. */ | ||||
| std::string label_{}; | std::string label_{}; | ||||
| /** Every item gets a button of type during the layout building #UI_BTYPE_TREEROW. */ | /** Every visible item gets a button of type #UI_BTYPE_TREEROW during the layout building. */ | ||||
| uiButTreeRow *tree_row_but_ = nullptr; | uiButTreeRow *tree_row_but_ = nullptr; | ||||
| public: | public: | ||||
| virtual ~AbstractTreeViewItem() = default; | virtual ~AbstractTreeViewItem() = default; | ||||
| virtual void build_row(uiLayout &row) = 0; | virtual void build_row(uiLayout &row) = 0; | ||||
| virtual void build_context_menu(bContext &C, uiLayout &column) const; | virtual void build_context_menu(bContext &C, uiLayout &column) const; | ||||
| AbstractTreeView &get_tree_view() const; | |||||
| void begin_renaming(); | |||||
| void toggle_collapsed(); | |||||
| void set_collapsed(bool collapsed); | |||||
| /** | |||||
| * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we | |||||
| * can't be sure about the item state. | |||||
| */ | |||||
| bool is_collapsed() const; | |||||
| /** | |||||
| * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we | |||||
| * can't be sure about the item state. | |||||
| */ | |||||
| bool is_active() const; | |||||
| protected: | |||||
| /** | |||||
| * Called when the items state changes from inactive to active. | |||||
| */ | |||||
| virtual void on_activate(); | virtual void on_activate(); | ||||
| /** | |||||
| * If the result is not empty, it controls whether the item should be active or not, | |||||
| * usually depending on the data that the view represents. | |||||
| */ | |||||
| virtual std::optional<bool> should_be_active() const; | |||||
| /** | /** | ||||
| * Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if | * Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if | ||||
| * another item is already being renamed. | * another item is already being renamed. | ||||
| */ | */ | ||||
| virtual bool can_rename() const; | virtual bool supports_renaming() const; | ||||
| /** | /** | ||||
| * Try renaming the item, or the data it represents. Can assume | * Try renaming the item, or the data it represents. Can assume | ||||
| * #AbstractTreeViewItem::can_rename() returned true. Sub-classes that override this should | * #AbstractTreeViewItem::supports_renaming() returned true. Sub-classes that override this | ||||
| * usually call this, unless they have a custom #AbstractTreeViewItem.matches(). | * should usually call this, unless they have a custom #AbstractTreeViewItem.matches(). | ||||
| * | * | ||||
| * \return True if the renaming was successful. | * \return True if the renaming was successful. | ||||
| */ | */ | ||||
| virtual bool rename(StringRefNull new_name); | virtual bool rename(StringRefNull new_name); | ||||
| /** | /** | ||||
| * Return whether the item can be collapsed. Used to disable collapsing for items with children. | |||||
| */ | |||||
| virtual bool supports_collapsing() const; | |||||
| /** | |||||
| * Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of | * Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of | ||||
| * the last redraw to this item. If sub-classes introduce more advanced state they should | * the last redraw to this item. If sub-classes introduce more advanced state they should | ||||
| * override this and make it update their state accordingly. | * override this and make it update their state accordingly. | ||||
| */ | */ | ||||
| virtual void update_from_old(const AbstractTreeViewItem &old); | virtual void update_from_old(const AbstractTreeViewItem &old); | ||||
| /** | /** | ||||
| * Compare this item to \a other to check if they represent the same data. | * Compare this item to \a other to check if they represent the same data. | ||||
| * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. | * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. | ||||
| * open/closed, active, etc.). Items are only matched if their parents also match. | * open/closed, active, etc.). Items are only matched if their parents also match. | ||||
| * By default this just matches the item's label (if the parents match!). If that isn't | * By default this just matches the item's label (if the parents match!). If that isn't | ||||
| * good enough for a sub-class, that can override it. | * good enough for a sub-class, that can override it. | ||||
| */ | */ | ||||
| virtual bool matches(const AbstractTreeViewItem &other) const; | virtual bool matches(const AbstractTreeViewItem &other) const; | ||||
| /** | /** | ||||
| * If an item wants to support being dragged, it has to return a drag controller here. | * If an item wants to support being dragged, it has to return a drag controller here. | ||||
| * That is an object implementing #AbstractTreeViewItemDragController. | * That is an object implementing #AbstractTreeViewItemDragController. | ||||
| */ | */ | ||||
| virtual std::unique_ptr<AbstractTreeViewItemDragController> create_drag_controller() const; | virtual std::unique_ptr<AbstractTreeViewItemDragController> create_drag_controller() const; | ||||
| /** | /** | ||||
| * If an item wants to support dropping data into it, it has to return a drop controller here. | * If an item wants to support dropping data into it, it has to return a drop controller here. | ||||
| * That is an object implementing #AbstractTreeViewItemDropController. | * That is an object implementing #AbstractTreeViewItemDropController. | ||||
| * | * | ||||
| * \note This drop controller may be requested for each event. The tree-view doesn't keep a drop | * \note This drop controller may be requested for each event. The tree-view doesn't keep a drop | ||||
| * controller around currently. So it can not contain persistent state. | * controller around currently. So it can not contain persistent state. | ||||
| */ | */ | ||||
| virtual std::unique_ptr<AbstractTreeViewItemDropController> create_drop_controller() const; | virtual std::unique_ptr<AbstractTreeViewItemDropController> create_drop_controller() const; | ||||
| void begin_renaming(); | |||||
| void end_renaming(); | |||||
| AbstractTreeView &get_tree_view() const; | |||||
| int count_parents() const; | |||||
| void deactivate(); | |||||
| /** | /** | ||||
| * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we | * Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate() | ||||
| * can't be sure about the item state. | * function and ensures this item's parents are not collapsed (so the item is visible). | ||||
| * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise the | |||||
| * actual item state is unknown, possibly calling state-change update functions incorrectly. | |||||
| */ | */ | ||||
| bool is_active() const; | void activate(); | ||||
| void deactivate(); | |||||
| /** | /** | ||||
| * Can be called from the #AbstractTreeViewItem::build_row() implementation, but not earlier. The | * Can be called from the #AbstractTreeViewItem::build_row() implementation, but not earlier. The | ||||
| * hovered state can't be queried reliably otherwise. | * hovered state can't be queried reliably otherwise. | ||||
| * Note that this does a linear lookup in the old block, so isn't too great performance-wise. | * Note that this does a linear lookup in the old block, so isn't too great performance-wise. | ||||
| */ | */ | ||||
| bool is_hovered() const; | bool is_hovered() const; | ||||
| void toggle_collapsed(); | |||||
| /** | |||||
| * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we | |||||
| * can't be sure about the item state. | |||||
| */ | |||||
| bool is_collapsed() const; | |||||
| void set_collapsed(bool collapsed); | |||||
| bool is_collapsible() const; | bool is_collapsible() const; | ||||
| bool is_renaming() const; | bool is_renaming() const; | ||||
| void ensure_parents_uncollapsed(); | void ensure_parents_uncollapsed(); | ||||
| bool matches_including_parents(const AbstractTreeViewItem &other) const; | |||||
| uiButTreeRow *tree_row_button(); | uiButTreeRow *tree_row_button(); | ||||
| protected: | |||||
| /** | |||||
| * Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate() | |||||
| * function and ensures this item's parents are not collapsed (so the item is visible). | |||||
| * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise the | |||||
| * actual item state is unknown, possibly calling state-change update functions incorrectly. | |||||
| */ | |||||
| void activate(); | |||||
| /** | |||||
| * If the result is not empty, it controls whether the item should be active or not, | |||||
| * usually depending on the data that the view represents. | |||||
| */ | |||||
| virtual std::optional<bool> should_be_active() const; | |||||
| /** | |||||
| * Return whether the item can be collapsed. Used to disable collapsing for items with children. | |||||
| */ | |||||
| virtual bool supports_collapsing() const; | |||||
| private: | private: | ||||
| static void rename_button_fn(bContext *, void *, char *); | static void rename_button_fn(bContext *, void *, char *); | ||||
| static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but); | static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but); | ||||
| static void tree_row_click_fn(struct bContext *, void *, void *); | static void tree_row_click_fn(struct bContext *, void *, void *); | ||||
| static void collapse_chevron_click_fn(bContext *, void *but_arg1, void *); | static void collapse_chevron_click_fn(bContext *, void *but_arg1, void *); | ||||
| static bool is_collapse_chevron_but(const uiBut *but); | static bool is_collapse_chevron_but(const uiBut *but); | ||||
| /** See #AbstractTreeView::change_state_delayed() */ | /** See #AbstractTreeView::change_state_delayed() */ | ||||
| void change_state_delayed(); | void change_state_delayed(); | ||||
| void end_renaming(); | |||||
| void add_treerow_button(uiBlock &block); | void add_treerow_button(uiBlock &block); | ||||
| void add_indent(uiLayout &row) const; | void add_indent(uiLayout &row) const; | ||||
| void add_collapse_chevron(uiBlock &block) const; | void add_collapse_chevron(uiBlock &block) const; | ||||
| void add_rename_button(uiLayout &row); | void add_rename_button(uiLayout &row); | ||||
| bool matches_including_parents(const AbstractTreeViewItem &other) const; | |||||
| bool has_active_child() const; | bool has_active_child() const; | ||||
| int count_parents() const; | |||||
| }; | }; | ||||
| /** \} */ | /** \} */ | ||||
| /* ---------------------------------------------------------------------- */ | /* ---------------------------------------------------------------------- */ | ||||
| /** \name Drag 'n Drop | /** \name Drag 'n Drop | ||||
| * \{ */ | * \{ */ | ||||
| ▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Lines | private: | ||||
| std::optional<bool> should_be_active() const override; | std::optional<bool> should_be_active() const override; | ||||
| void on_activate() override; | void on_activate() override; | ||||
| }; | }; | ||||
| /** \} */ | /** \} */ | ||||
| /* ---------------------------------------------------------------------- */ | /* ---------------------------------------------------------------------- */ | ||||
| /** \name Tree-View Builder | |||||
| * \{ */ | |||||
| class TreeViewBuilder { | |||||
| uiBlock &block_; | |||||
| public: | |||||
| TreeViewBuilder(uiBlock &block); | |||||
| void build_tree_view(AbstractTreeView &tree_view); | |||||
| }; | |||||
| /** \} */ | |||||
| /* ---------------------------------------------------------------------- */ | |||||
| template<class ItemT, typename... Args> | template<class ItemT, typename... Args> | ||||
| inline ItemT &TreeViewItemContainer::add_tree_item(Args &&...args) | inline ItemT &TreeViewItemContainer::add_tree_item(Args &&...args) | ||||
| { | { | ||||
| static_assert(std::is_base_of<AbstractTreeViewItem, ItemT>::value, | static_assert(std::is_base_of<AbstractTreeViewItem, ItemT>::value, | ||||
| "Type must derive from and implement the AbstractTreeViewItem interface"); | "Type must derive from and implement the AbstractTreeViewItem interface"); | ||||
| return dynamic_cast<ItemT &>( | return dynamic_cast<ItemT &>( | ||||
| Show All 18 Lines | |||||