Blender V5.0
interface_template_bone_collection_tree.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Foundation
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_context.hh"
10
11#include "BLT_translation.hh"
12
13#include "ANIM_armature_iter.hh"
15
16#include "UI_interface.hh"
17#include "UI_tree_view.hh"
18
19#include "RNA_access.hh"
20#include "RNA_prototypes.hh"
21
22#include "ED_armature.hh"
23#include "ED_undo.hh"
24
25#include "WM_api.hh"
26
27#include <fmt/format.h>
28
30
31using namespace blender::animrig;
32
34 protected:
37
38 public:
39 explicit BoneCollectionTreeView(bArmature &armature);
40 void build_tree() override;
41
42 bool listen(const wmNotifier &notifier) const override;
43
44 private:
45 void build_tree_node_recursive(TreeViewItemContainer &parent, const int bcoll_index);
46
49 void build_bcolls_with_selected_bones();
50};
51
58
64
65 const BoneCollection &bcoll() const
66 {
67 return *armature->collection_array[bcoll_index];
68 }
70 {
71 return *armature->collection_array[bcoll_index];
72 }
73};
74
76 private:
77 ArmatureBoneCollection drag_arm_bcoll_;
78
79 public:
81 bArmature &armature,
82 const int bcoll_index);
83
84 std::optional<eWM_DragDataType> get_drag_type() const override;
85 void *create_drag_data() const override;
86 void on_drag_start(bContext &C) override;
87};
88
90 private:
91 ArmatureBoneCollection drop_bonecoll_;
92
93 public:
95 DropBehavior behavior,
96 const ArmatureBoneCollection &drop_bonecoll)
97 : TreeViewItemDropTarget(item, behavior), drop_bonecoll_(drop_bonecoll)
98 {
99 }
100
101 bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
102 {
103 if (drag.type != WM_DRAG_BONE_COLLECTION) {
104 return false;
105 }
106
107 const ArmatureBoneCollection *drag_arm_bcoll = static_cast<const ArmatureBoneCollection *>(
108 drag.poin);
109
110 /* Do not allow dropping onto another armature. */
111 if (drag_arm_bcoll->armature != drop_bonecoll_.armature) {
112 *r_disabled_hint = "Cannot drag & drop bone collections between Armatures.";
113 return false;
114 }
115
116 /* Dragging onto itself doesn't do anything. */
117 if (drag_arm_bcoll->bcoll_index == drop_bonecoll_.bcoll_index) {
118 return false;
119 }
120
121 /* Do not allow dropping onto its own descendants. */
123 drag_arm_bcoll->armature, drag_arm_bcoll->bcoll_index, drop_bonecoll_.bcoll_index))
124 {
125 *r_disabled_hint = "Cannot drag a collection onto a descendent";
126 return false;
127 }
128
129 return true;
130 }
131
132 std::string drop_tooltip(const DragInfo &drag_info) const override
133 {
134 const ArmatureBoneCollection *drag_bone_collection =
135 static_cast<const ArmatureBoneCollection *>(drag_info.drag_data.poin);
136 const BoneCollection &drag_bcoll = drag_bone_collection->bcoll();
137 const BoneCollection &drop_bcoll = drop_bonecoll_.bcoll();
138
139 const StringRef drag_name = drag_bcoll.name;
140 const StringRef drop_name = drop_bcoll.name;
141
142 switch (drag_info.drop_location) {
144 return fmt::format(fmt::runtime(TIP_("Move {} into {}")), drag_name, drop_name);
146 return fmt::format(fmt::runtime(TIP_("Move {} above {}")), drag_name, drop_name);
148 return fmt::format(fmt::runtime(TIP_("Move {} below {}")), drag_name, drop_name);
149 }
150
151 return "";
152 }
153
154 bool on_drop(bContext *C, const DragInfo &drag_info) const override
155 {
156 const ArmatureBoneCollection *drag_arm_bcoll = static_cast<const ArmatureBoneCollection *>(
157 drag_info.drag_data.poin);
158 bArmature *arm = drop_bonecoll_.armature;
159
160 const int from_bcoll_index = drag_arm_bcoll->bcoll_index;
161 const int to_bcoll_index = drop_bonecoll_.bcoll_index;
162
163 int new_bcoll_index = -1;
164 switch (drag_info.drop_location) {
167 arm, from_bcoll_index, to_bcoll_index, MoveLocation::Before);
168 break;
169
170 case DropLocation::Into: {
171 if (!ANIM_armature_bonecoll_is_editable(arm, &drop_bonecoll_.bcoll())) {
172 return false;
173 }
174
175 const int from_parent_index = armature_bonecoll_find_parent_index(arm, from_bcoll_index);
176 /* The bone collection becomes the last child of the new parent, as
177 * that's consistent with the drag & drop of scene collections in the
178 * outliner. */
179 new_bcoll_index = armature_bonecoll_move_to_parent(
180 arm, from_bcoll_index, -1, from_parent_index, to_bcoll_index);
181 break;
182 }
185 arm, from_bcoll_index, to_bcoll_index, MoveLocation::After);
186 break;
187 }
188
189 if (new_bcoll_index < 0) {
190 return false;
191 }
192
193 ANIM_armature_bonecoll_active_index_set(arm, new_bcoll_index);
195
196 ED_undo_push(C, "Reorder Armature Bone Collections");
197 return true;
198 }
199};
200
202 private:
203 bArmature &armature_;
204 int bcoll_index_;
205 BoneCollection &bone_collection_;
206 bool has_any_selected_bones_;
207
208 public:
209 BoneCollectionItem(bArmature &armature, const int bcoll_index, const bool has_any_selected_bones)
210 : armature_(armature),
211 bcoll_index_(bcoll_index),
212 bone_collection_(*armature.collection_array[bcoll_index]),
213 has_any_selected_bones_(has_any_selected_bones)
214 {
215 this->label_ = bone_collection_.name;
216 }
217
218 void build_row(uiLayout &row) override
219 {
220 uiLayout *sub = &row.row(true);
221
222 uiBut *name_label = uiItemL_ex(sub, bone_collection_.name, ICON_NONE, false, false);
223 if (!ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_)) {
225 }
226
227 /* Contains Active Bone icon. */
228 /* Performance note: this check potentially loops over all bone collections the active bone is
229 * assigned to. And this happens for each redraw of each bone collection in the armature. */
230 {
231 int icon;
232 if (ANIM_armature_bonecoll_contains_active_bone(&armature_, &bone_collection_)) {
233 icon = ICON_LAYER_ACTIVE;
234 }
235 else if (has_any_selected_bones_) {
236 icon = ICON_LAYER_USED;
237 }
238 else {
239 icon = ICON_BLANK1;
240 }
241 sub->label("", icon);
242 }
243
244 /* Visibility eye icon. */
245 {
246 const bool is_solo_active = armature_.flag & ARM_BCOLL_SOLO_ACTIVE;
247 uiLayout *visibility_sub = &sub->row(true);
248 visibility_sub->active_set(!is_solo_active && bone_collection_.is_visible_ancestors());
249
250 const int icon = bone_collection_.is_visible() ? ICON_HIDE_OFF : ICON_HIDE_ON;
251 PointerRNA bcoll_ptr = rna_pointer();
252 visibility_sub->prop(&bcoll_ptr, "is_visible", UI_ITEM_R_ICON_ONLY, "", icon);
253 }
254
255 /* Solo icon. */
256 {
257 const int icon = bone_collection_.is_solo() ? ICON_SOLO_ON : ICON_SOLO_OFF;
258 PointerRNA bcoll_ptr = rna_pointer();
259 sub->prop(&bcoll_ptr, "is_solo", UI_ITEM_R_ICON_ONLY, "", icon);
260 }
261 }
262
263 void build_context_menu(bContext &C, uiLayout &column) const override
264 {
265 MenuType *mt = WM_menutype_find("ARMATURE_MT_collection_tree_context_menu", true);
266 if (!mt) {
267 return;
268 }
269 UI_menutype_draw(&C, mt, &column);
270 }
271
272 std::optional<bool> should_be_active() const override
273 {
274 return armature_.runtime.active_collection_index == bcoll_index_;
275 }
276
277 void on_activate(bContext &C) override
278 {
279 /* Let RNA handle the property change. This makes sure all the notifiers and DEG
280 * update calls are properly called. */
282 &armature_.id, &RNA_BoneCollections, &armature_);
283 PropertyRNA *prop = RNA_struct_find_property(&bcolls_ptr, "active_index");
284
285 RNA_property_int_set(&bcolls_ptr, prop, bcoll_index_);
286 RNA_property_update(&C, &bcolls_ptr, prop);
287
288 ED_undo_push(&C, "Change Armature's Active Bone Collection");
289 }
290
291 std::optional<bool> should_be_collapsed() const override
292 {
293 const bool is_collapsed = !bone_collection_.is_expanded();
294 return is_collapsed;
295 }
296
297 bool set_collapsed(const bool collapsed) override
298 {
299 if (!AbstractTreeViewItem::set_collapsed(collapsed)) {
300 return false;
301 }
302
303 /* Ensure that the flag in DNA is set. */
304 ANIM_armature_bonecoll_is_expanded_set(&bone_collection_, !collapsed);
305 return true;
306 }
307
308 void on_collapse_change(bContext &C, const bool is_collapsed) override
309 {
310 const bool is_expanded = !is_collapsed;
311
312 /* Let RNA handle the property change. This makes sure all the notifiers and DEG
313 * update calls are properly called. */
315 &armature_.id, &RNA_BoneCollection, &bone_collection_);
316 PropertyRNA *prop = RNA_struct_find_property(&bcoll_ptr, "is_expanded");
317
318 RNA_property_boolean_set(&bcoll_ptr, prop, is_expanded);
319 RNA_property_update(&C, &bcoll_ptr, prop);
320 }
321
322 bool supports_renaming() const override
323 {
324 return ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_);
325 }
326
327 bool rename(const bContext &C, StringRefNull new_name) override
328 {
329 /* Let RNA handle the renaming. This makes sure all the notifiers and DEG
330 * update calls are properly called. */
331 PointerRNA bcoll_ptr = rna_pointer();
332 PropertyRNA *prop = RNA_struct_find_property(&bcoll_ptr, "name");
333
334 RNA_property_string_set(&bcoll_ptr, prop, new_name.c_str());
335 RNA_property_update(&const_cast<bContext &>(C), &bcoll_ptr, prop);
336
337 ED_undo_push(&const_cast<bContext &>(C), "Rename Armature Bone Collection");
338 return true;
339 }
340
342 {
343 return bone_collection_.name;
344 }
345
346 void delete_item(bContext *C) override
347 {
348 ANIM_armature_bonecoll_remove(&armature_, &bone_collection_);
349 ED_undo_push(C, "Delete Bone Collection");
350 }
351 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
352 {
353 /* Reject dragging linked (or otherwise uneditable) bone collections. */
354 if (!ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_)) {
355 return {};
356 }
357
358 BoneCollectionTreeView &tree_view = static_cast<BoneCollectionTreeView &>(get_tree_view());
359 return std::make_unique<BoneCollectionDragController>(tree_view, armature_, bcoll_index_);
360 }
361
362 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
363 {
364 ArmatureBoneCollection drop_bonecoll(&armature_, bcoll_index_);
365 /* For now, only support DropBehavior::Insert until there's code for actually reordering
366 * siblings. Currently only 'move to another parent' is implemented. */
367 return std::make_unique<BoneCollectionDropTarget>(
368 *this, DropBehavior::ReorderAndInsert, drop_bonecoll);
369 }
370
371 protected:
374 {
375 return RNA_pointer_create_discrete(&armature_.id, &RNA_BoneCollection, &bone_collection_);
376 }
377};
378
380
382{
383 build_bcolls_with_selected_bones();
384
385 for (int bcoll_index = 0; bcoll_index < armature_.collection_root_count; bcoll_index++) {
386 build_tree_node_recursive(*this, bcoll_index);
387 }
388}
389
390void BoneCollectionTreeView::build_tree_node_recursive(TreeViewItemContainer &parent,
391 const int bcoll_index)
392{
393 BoneCollection *bcoll = armature_.collection_array[bcoll_index];
394 const bool has_any_selected_bones = bcolls_with_selected_bones_.contains(bcoll);
395 BoneCollectionItem &bcoll_tree_item = parent.add_tree_item<BoneCollectionItem>(
396 armature_, bcoll_index, has_any_selected_bones);
397 for (int child_index = bcoll->child_index; child_index < bcoll->child_index + bcoll->child_count;
398 child_index++)
399 {
400 build_tree_node_recursive(bcoll_tree_item, child_index);
401 }
402}
403
405{
406 return notifier.data == ND_BONE_COLLECTION;
407}
408
409void BoneCollectionTreeView::build_bcolls_with_selected_bones()
410{
412
413 /* Armature Edit mode. */
414 if (armature_.edbo) {
416 if ((ebone->flag & BONE_SELECTED) == 0) {
417 continue;
418 }
419
420 LISTBASE_FOREACH (BoneCollectionReference *, ref, &ebone->bone_collections) {
422 }
423 }
424 return;
425 }
426
427 /* Any other mode. */
428 ANIM_armature_foreach_bone(&armature_.bonebase, [&](const Bone *bone) {
429 if ((bone->flag & BONE_SELECTED) == 0) {
430 return;
431 }
432
434 bcolls_with_selected_bones_.add(ref->bcoll);
435 }
436 });
437}
438
440 bArmature &armature,
441 const int bcoll_index)
442 : AbstractViewItemDragController(tree_view), drag_arm_bcoll_(&armature, bcoll_index)
443{
444}
445
446std::optional<eWM_DragDataType> BoneCollectionDragController::get_drag_type() const
447{
449}
450
452{
454 *drag_data = drag_arm_bcoll_;
455 return drag_data;
456}
457
459{
460 ANIM_armature_bonecoll_active_index_set(drag_arm_bcoll_.armature, drag_arm_bcoll_.bcoll_index);
461}
462
463} // namespace blender::ui::bonecollections
464
466{
467 using namespace blender;
468
469 bArmature *armature = ED_armature_context(C);
470 if (armature == nullptr) {
471 return;
472 }
473 BLI_assert(GS(armature->id.name) == ID_AR);
474
475 uiBlock *block = layout->block();
476
478 *block,
479 "Bone Collection Tree View",
480 std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*armature));
481 tree_view->set_context_menu_title("Bone Collection");
482 tree_view->set_default_rows(5);
483
484 ui::TreeViewBuilder::build_tree_view(*C, *tree_view, *layout);
485}
Iterators for armatures.
C++ functions to deal with Armature collections (i.e. the successor of bone layers).
bool ANIM_armature_bonecoll_is_editable(const bArmature *armature, const BoneCollection *bcoll)
void ANIM_armature_bonecoll_is_expanded_set(BoneCollection *bcoll, bool is_expanded)
int ANIM_armature_bonecoll_move_before_after_index(bArmature *armature, int from_index, int to_index, MoveLocation before_after)
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
bool ANIM_armature_bonecoll_contains_active_bone(const bArmature *armature, const BoneCollection *bcoll)
void ANIM_armature_bonecoll_active_index_set(bArmature *armature, int bone_collection_index)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
#define TIP_(msgid)
@ ID_AR
struct BoneCollectionReference BoneCollectionReference
struct Bone Bone
@ BONE_SELECTED
@ ARM_BCOLL_SOLO_ACTIVE
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
#define C
Definition RandGen.cpp:29
blender::ui::AbstractGridView * UI_block_add_view(uiBlock &block, blender::StringRef idname, std::unique_ptr< blender::ui::AbstractGridView > grid_view)
@ UI_BUT_INACTIVE
void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C)
void UI_but_flag_enable(uiBut *but, int flag)
@ UI_ITEM_R_ICON_ONLY
void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout)
uiBut * uiItemL_ex(uiLayout *layout, blender::StringRef name, int icon, bool highlight, bool redalert)
@ WM_DRAG_BONE_COLLECTION
Definition WM_types.hh:1224
#define ND_BONE_COLLECTION
Definition WM_types.hh:474
#define NC_OBJECT
Definition WM_types.hh:379
bArmature * ED_armature_context(const bContext *C)
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
void clear()
Definition BLI_set.hh:551
constexpr const char * c_str() const
Abstract base class for defining a customizable tree-view item.
AbstractTreeView & get_tree_view() const
Definition tree_view.cc:644
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:715
void set_default_rows(int default_rows)
Definition tree_view.cc:141
void set_context_menu_title(const std::string &title)
static void build_tree_view(const bContext &C, AbstractTreeView &tree_view, uiLayout &layout, bool add_box=true)
ItemT & add_tree_item(Args &&...args)
TreeViewItemDropTarget(AbstractTreeViewItem &view_item, DropBehavior behavior=DropBehavior::Insert)
Definition tree_view.cc:437
BoneCollectionDragController(BoneCollectionTreeView &tree_view, bArmature &armature, const int bcoll_index)
bool on_drop(bContext *C, const DragInfo &drag_info) const override
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
std::string drop_tooltip(const DragInfo &drag_info) const override
BoneCollectionDropTarget(AbstractTreeViewItem &item, DropBehavior behavior, const ArmatureBoneCollection &drop_bonecoll)
std::unique_ptr< TreeViewItemDropTarget > create_drop_target() override
void on_collapse_change(bContext &C, const bool is_collapsed) override
void build_context_menu(bContext &C, uiLayout &column) const override
BoneCollectionItem(bArmature &armature, const int bcoll_index, const bool has_any_selected_bones)
bool rename(const bContext &C, StringRefNull new_name) override
std::unique_ptr< AbstractViewItemDragController > create_drag_controller() const override
#define GS(x)
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
static void ANIM_armature_foreach_bone(ListBase *bones, CB callback)
bool armature_bonecoll_is_descendant_of(const bArmature *armature, int potential_parent_index, int potential_descendant_index)
int armature_bonecoll_find_parent_index(const bArmature *armature, int bcoll_index)
int armature_bonecoll_move_to_parent(bArmature *armature, int from_bcoll_index, int to_child_num, int from_parent_index, int to_parent_index)
void RNA_property_int_set(PointerRNA *ptr, PropertyRNA *prop, int value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
Bone_Runtime runtime
char name[258]
Definition DNA_ID.h:432
struct BoneCollection ** collection_array
ListBase * edbo
const wmDrag & drag_data
const DropLocation drop_location
uiBlock * block() const
void label(blender::StringRef name, int icon)
void active_set(bool active)
uiLayout & row(bool align)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
eWM_DragDataType type
Definition WM_types.hh:1331
void * poin
Definition WM_types.hh:1332
unsigned int data
Definition WM_types.hh:358
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
MenuType * WM_menutype_find(const StringRef idname, bool quiet)