diff options
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 2 | ||||
-rw-r--r-- | source/blender/editors/include/UI_tree_view.hh | 44 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 22 | ||||
-rw-r--r-- | source/blender/editors/interface/tree_view.cc | 225 | ||||
-rw-r--r-- | source/blender/editors/space_file/asset_catalog_tree_view.cc | 26 |
5 files changed, 278 insertions, 41 deletions
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index f642895f64e..e8b71a41439 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -2770,6 +2770,8 @@ char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, const struct bContext *C, const struct wmDrag *drag, const struct wmEvent *event); +bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle); +void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle); uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, int x, int y); diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index dbafd1b3a2b..51737067648 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -23,10 +23,13 @@ #pragma once +#include <array> #include <functional> #include <memory> #include <string> +#include "DNA_defs.h" + #include "BLI_function_ref.hh" #include "BLI_vector.hh" @@ -144,8 +147,14 @@ class TreeViewLayoutBuilder { * \{ */ class AbstractTreeView : public TreeViewItemContainer { + friend AbstractTreeViewItem; friend TreeViewBuilder; - friend TreeViewLayoutBuilder; + + /** + * Only one item can be renamed at a time. So the tree is informed about the renaming state to + * enforce that. + */ + std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_; bool is_reconstructed_ = false; @@ -154,6 +163,8 @@ class AbstractTreeView : public TreeViewItemContainer { void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; + /** Only one item can be renamed at a time. */ + bool is_renaming() const; /** * Check if the tree is fully (re-)constructed. That means, both #build_tree() and * #update_from_old() have finished. @@ -198,6 +209,7 @@ class AbstractTreeView : public TreeViewItemContainer { */ class AbstractTreeViewItem : public TreeViewItemContainer { friend class AbstractTreeView; + friend class TreeViewLayoutBuilder; public: using IsActiveFn = std::function<bool()>; @@ -205,12 +217,15 @@ class AbstractTreeViewItem : public TreeViewItemContainer { private: bool is_open_ = false; bool is_active_ = false; + bool is_renaming_ = false; IsActiveFn is_active_fn_; protected: /** This label is used for identifying an item (together with its parent's labels). */ std::string label_{}; + /** Every item gets a button of type during the layout building #UI_BTYPE_TREEROW. */ + uiButTreeRow *tree_row_but_ = nullptr; public: virtual ~AbstractTreeViewItem() = default; @@ -234,6 +249,19 @@ class AbstractTreeViewItem : public TreeViewItemContainer { virtual std::string drop_tooltip(const bContext &C, const wmDrag &drag, const wmEvent &event) const; + /** + * Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if + * another item is already being renamed. + */ + virtual bool can_rename() const; + /** + * Try renaming the item, or the data it represents. Can assume + * #AbstractTreeViewItem::can_rename() returned true. Sub-classes that override this should + * usually call this, unless they have a custom #AbstractTreeViewItem.matches(). + * + * \return True if the renaming was successful. + */ + virtual bool rename(StringRefNull new_name); /** * Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of @@ -250,7 +278,11 @@ class AbstractTreeViewItem : public TreeViewItemContainer { */ virtual bool matches(const AbstractTreeViewItem &other) const; + void begin_renaming(); + void end_renaming(); + const AbstractTreeView &get_tree_view() const; + AbstractTreeView &get_tree_view(); int count_parents() const; void deactivate(); /** @@ -266,6 +298,8 @@ class AbstractTreeViewItem : public TreeViewItemContainer { bool is_collapsed() const; void set_collapsed(bool collapsed); bool is_collapsible() const; + bool is_renaming() const; + void ensure_parents_uncollapsed(); protected: @@ -278,8 +312,14 @@ class AbstractTreeViewItem : public TreeViewItemContainer { void activate(); private: + static void rename_button_fn(bContext *, void *, char *); + static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but); + static void tree_row_click_fn(struct bContext *, void *, void *); + /** See #AbstractTreeView::change_state_delayed() */ void change_state_delayed(); + void add_treerow_button(uiBlock &block); + void add_rename_button(uiBlock &block); }; /** \} */ @@ -304,8 +344,6 @@ class BasicTreeViewItem : public AbstractTreeViewItem { void on_activate(ActivateFn fn); protected: - /** Created in the #build() function. */ - uiButTreeRow *tree_row_but_ = nullptr; /** * Optionally passed to the #BasicTreeViewItem constructor. Called when activating this tree * view item. This way users don't have to sub-class #BasicTreeViewItem, just to implement diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 6ee563003ef..f73420b3668 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -4822,6 +4822,24 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons return WM_UI_HANDLER_CONTINUE; } +static int ui_do_but_TREEROW(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + BLI_assert(tree_row_but->but.type == UI_BTYPE_TREEROW); + + if ((event->type == LEFTMOUSE) && (event->val == KM_DBL_CLICK)) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + + UI_tree_view_item_begin_rename(tree_row_but->tree_item); + return WM_UI_HANDLER_BREAK; + } + + return ui_do_but_TOG(C, but, data, event); +} + static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -7989,10 +8007,12 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_CHECKBOX: case UI_BTYPE_CHECKBOX_N: case UI_BTYPE_ROW: - case UI_BTYPE_TREEROW: case UI_BTYPE_DATASETROW: retval = ui_do_but_TOG(C, but, data, event); break; + case UI_BTYPE_TREEROW: + retval = ui_do_but_TREEROW(C, but, data, event); + break; case UI_BTYPE_SCROLL: retval = ui_do_but_SCROLL(C, block, but, data, event); break; diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc index 8bd2be7dc77..8f272143b2c 100644 --- a/source/blender/editors/interface/tree_view.cc +++ b/source/blender/editors/interface/tree_view.cc @@ -20,6 +20,8 @@ #include "DNA_userdef_types.h" +#include "BKE_context.h" + #include "BLT_translation.h" #include "interface_intern.h" @@ -76,6 +78,11 @@ void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) con foreach_item_recursive(iter_fn, options); } +bool AbstractTreeView::is_renaming() const +{ + return rename_buffer_ != nullptr; +} + void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder) { uiLayout *prev_layout = builder.current_layout(); @@ -103,6 +110,13 @@ void AbstractTreeView::update_from_old(uiBlock &new_block) BLI_assert(old_view_handle); AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle); + + /* Update own persistent data. */ + /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's + * pointer to identify itself over redraws. */ + rename_buffer_ = std::move(old_view.rename_buffer_); + old_view.rename_buffer_ = nullptr; + update_children_from_old_recursive(*this, old_view); /* Finished (re-)constructing the tree. */ @@ -153,6 +167,95 @@ void AbstractTreeView::change_state_delayed() /* ---------------------------------------------------------------------- */ +void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, + void *but_arg1, + void * /*arg2*/) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; + BasicTreeViewItem &tree_item = reinterpret_cast<BasicTreeViewItem &>(*tree_row_but->tree_item); + + /* Let a click on an opened item activate it, a second click will close it then. + * TODO Should this be for asset catalogs only? */ + if (tree_item.is_collapsed() || tree_item.is_active()) { + tree_item.toggle_collapsed(); + } + tree_item.activate(); +} + +void AbstractTreeViewItem::add_treerow_button(uiBlock &block) +{ + tree_row_but_ = (uiButTreeRow *)uiDefBut( + &block, UI_BTYPE_TREEROW, 0, "", 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + + tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this); + UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); + UI_but_treerow_indentation_set(&tree_row_but_->but, count_parents()); +} + +AbstractTreeViewItem *AbstractTreeViewItem::find_tree_item_from_rename_button( + const uiBut &rename_but) +{ + /* A minimal sanity check, can't do much more here. */ + BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); + + LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { + if (but->type != UI_BTYPE_TREEROW) { + continue; + } + + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + AbstractTreeViewItem *item = reinterpret_cast<AbstractTreeViewItem *>(tree_row_but->tree_item); + const AbstractTreeView &tree_view = item->get_tree_view(); + + if (item->is_renaming() && (tree_view.rename_buffer_->data() == rename_but.poin)) { + return item; + } + } + + return nullptr; +} + +void AbstractTreeViewItem::rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) +{ + const uiBut *rename_but = static_cast<uiBut *>(arg); + AbstractTreeViewItem *item = find_tree_item_from_rename_button(*rename_but); + BLI_assert(item); + + const AbstractTreeView &tree_view = item->get_tree_view(); + item->rename(tree_view.rename_buffer_->data()); + item->end_renaming(); +} + +void AbstractTreeViewItem::add_rename_button(uiBlock &block) +{ + AbstractTreeView &tree_view = get_tree_view(); + uiBut *rename_but = uiDefBut(&block, + UI_BTYPE_TEXT, + 1, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + tree_view.rename_buffer_->data(), + 1.0f, + tree_view.rename_buffer_->max_size(), + 0, + 0, + ""); + + /* Gotta be careful with what's passed to the `arg1` here. Any tree data will be freed once the + * callback is executed. */ + UI_but_func_rename_set(rename_but, AbstractTreeViewItem::rename_button_fn, rename_but); + + const bContext *evil_C = static_cast<bContext *>(block.evil_C); + ARegion *region = CTX_wm_region(evil_C); + /* Returns false if the button was removed. */ + if (UI_but_active_only(evil_C, region, &block, rename_but) == false) { + end_renaming(); + } +} + void AbstractTreeViewItem::on_activate() { /* Do nothing by default. */ @@ -181,10 +284,25 @@ std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/, return TIP_("Drop into/onto tree item"); } +bool AbstractTreeViewItem::can_rename() const +{ + /* No renaming by default. */ + return false; +} + +bool AbstractTreeViewItem::rename(StringRefNull new_name) +{ + /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches() + * recognizes the item. (It only compares labels by default.) */ + label_ = new_name; + return true; +} + void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) { is_open_ = old.is_open_; is_active_ = old.is_active_; + is_renaming_ = old.is_renaming_; } bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const @@ -192,11 +310,41 @@ bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const return label_ == other.label_; } +void AbstractTreeViewItem::begin_renaming() +{ + AbstractTreeView &tree_view = get_tree_view(); + if (tree_view.is_renaming() || !can_rename()) { + return; + } + + is_renaming_ = true; + + tree_view.rename_buffer_ = std::make_unique<decltype(tree_view.rename_buffer_)::element_type>(); + std::copy(std::begin(label_), std::end(label_), std::begin(*tree_view.rename_buffer_)); +} + +void AbstractTreeViewItem::end_renaming() +{ + if (!is_renaming()) { + return; + } + + is_renaming_ = false; + + AbstractTreeView &tree_view = get_tree_view(); + tree_view.rename_buffer_ = nullptr; +} + const AbstractTreeView &AbstractTreeViewItem::get_tree_view() const { return static_cast<AbstractTreeView &>(*root_); } +AbstractTreeView &AbstractTreeViewItem::get_tree_view() +{ + return static_cast<AbstractTreeView &>(*root_); +} + int AbstractTreeViewItem::count_parents() const { int i = 0; @@ -259,6 +407,11 @@ bool AbstractTreeViewItem::is_collapsible() const return !children_.is_empty(); } +bool AbstractTreeViewItem::is_renaming() const +{ + return is_renaming_; +} + void AbstractTreeViewItem::ensure_parents_uncollapsed() { for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { @@ -298,9 +451,21 @@ void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const uiLayout *prev_layout = current_layout(); uiLayout *row = uiLayoutRow(prev_layout, false); - item.build_row(*row); + uiLayoutOverlap(row); + + uiBlock &block_ = block(); + + /* Every item gets one! Other buttons can be overlapped on top. */ + item.add_treerow_button(block_); + + if (item.is_renaming()) { + item.add_rename_button(block_); + } + else { + item.build_row(*row); + } - UI_block_layout_set_current(&block(), prev_layout); + UI_block_layout_set_current(&block_, prev_layout); } uiBlock &TreeViewLayoutBuilder::block() const @@ -320,42 +485,12 @@ BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(ic label_ = label; } -void BasicTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, void *but_arg1, void * /*arg2*/) +void BasicTreeViewItem::build_row(uiLayout & /*row*/) { - uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; - BasicTreeViewItem &tree_item = reinterpret_cast<BasicTreeViewItem &>(*tree_row_but->tree_item); - - /* Let a click on an opened item activate it, a second click will close it then. - * TODO Should this be for asset catalogs only? */ - if (tree_item.is_collapsed() || tree_item.is_active()) { - tree_item.toggle_collapsed(); + if (BIFIconID icon = get_draw_icon()) { + ui_def_but_icon(&tree_row_but_->but, icon, UI_HAS_ICON); } - tree_item.activate(); -} - -void BasicTreeViewItem::build_row(uiLayout &row) -{ - uiBlock *block = uiLayoutGetBlock(&row); - tree_row_but_ = (uiButTreeRow *)uiDefIconTextBut(block, - UI_BTYPE_TREEROW, - 0, - /* TODO allow icon besides the chevron icon? */ - get_draw_icon(), - label_.data(), - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - - tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this); - UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); - UI_but_treerow_indentation_set(&tree_row_but_->but, count_parents()); + tree_row_but_->but.str = BLI_strdupn(label_.c_str(), label_.length()); } void BasicTreeViewItem::on_activate() @@ -437,3 +572,21 @@ bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase * return false; } + +/** + * Can \a item_handle be renamed right now? Not that this isn't just a mere wrapper around + * #AbstractTreeViewItem::can_rename(). This also checks if there is another item being renamed, + * and returns false if so. + */ +bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); + const AbstractTreeView &tree_view = item.get_tree_view(); + return !tree_view.is_renaming() && item.can_rename(); +} + +void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle) +{ + AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_handle); + item.begin_renaming(); +} diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 291582dac08..85912268286 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -52,6 +52,7 @@ using namespace blender::bke; namespace blender::ed::asset_browser { class AssetCatalogTreeView : public ui::AbstractTreeView { + bke::AssetCatalogService *catalog_service_; /** The asset catalog tree this tree-view represents. */ bke::AssetCatalogTree *catalog_tree_; FileAssetSelectParams *params_; @@ -99,6 +100,9 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { const wmDrag &drag, const wmEvent &event) const override; bool on_drop(const wmDrag &drag) override; + + bool can_rename() const override; + bool rename(StringRefNull new_name) override; }; /** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level @@ -124,7 +128,8 @@ class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params, SpaceFile &space_file) - : catalog_tree_(BKE_asset_library_get_catalog_tree(library)), + : catalog_service_(BKE_asset_library_get_catalog_service(library)), + catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params), space_file_(space_file) { @@ -309,6 +314,25 @@ bool AssetCatalogTreeViewItem::on_drop(const wmDrag &drag) tree_view, drag, catalog_item_.get_catalog_id(), catalog_item_.get_simple_name()); } +bool AssetCatalogTreeViewItem::can_rename() const +{ + return true; +} + +bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) +{ + /* Important to keep state. */ + BasicTreeViewItem::rename(new_name); + + const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( + get_tree_view()); + + AssetCatalogPath new_path = catalog_item_.catalog_path().parent(); + new_path = new_path / StringRef(new_name); + tree_view.catalog_service_->update_catalog_path(catalog_item_.get_catalog_id(), new_path); + return true; +} + /* ---------------------------------------------------------------------- */ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) |