Blender V4.5
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 eWM_DragDataType get_drag_type() const override;
85 void *create_drag_data() const override;
86 void on_drag_start() 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 uiLayoutSetActive(visibility_sub,
249 !is_solo_active && bone_collection_.is_visible_ancestors());
250
251 const int icon = bone_collection_.is_visible() ? ICON_HIDE_OFF : ICON_HIDE_ON;
252 PointerRNA bcoll_ptr = rna_pointer();
253 visibility_sub->prop(&bcoll_ptr, "is_visible", UI_ITEM_R_ICON_ONLY, "", icon);
254 }
255
256 /* Solo icon. */
257 {
258 const int icon = bone_collection_.is_solo() ? ICON_SOLO_ON : ICON_SOLO_OFF;
259 PointerRNA bcoll_ptr = rna_pointer();
260 sub->prop(&bcoll_ptr, "is_solo", UI_ITEM_R_ICON_ONLY, "", icon);
261 }
262 }
263
264 void build_context_menu(bContext &C, uiLayout &column) const override
265 {
266 MenuType *mt = WM_menutype_find("ARMATURE_MT_collection_tree_context_menu", true);
267 if (!mt) {
268 return;
269 }
270 UI_menutype_draw(&C, mt, &column);
271 }
272
273 std::optional<bool> should_be_active() const override
274 {
275 return armature_.runtime.active_collection_index == bcoll_index_;
276 }
277
278 void on_activate(bContext &C) override
279 {
280 /* Let RNA handle the property change. This makes sure all the notifiers and DEG
281 * update calls are properly called. */
283 &armature_.id, &RNA_BoneCollections, &armature_);
284 PropertyRNA *prop = RNA_struct_find_property(&bcolls_ptr, "active_index");
285
286 RNA_property_int_set(&bcolls_ptr, prop, bcoll_index_);
287 RNA_property_update(&C, &bcolls_ptr, prop);
288
289 ED_undo_push(&C, "Change Armature's Active Bone Collection");
290 }
291
292 std::optional<bool> should_be_collapsed() const override
293 {
294 const bool is_collapsed = !bone_collection_.is_expanded();
295 return is_collapsed;
296 }
297
298 bool set_collapsed(const bool collapsed) override
299 {
300 if (!AbstractTreeViewItem::set_collapsed(collapsed)) {
301 return false;
302 }
303
304 /* Ensure that the flag in DNA is set. */
305 ANIM_armature_bonecoll_is_expanded_set(&bone_collection_, !collapsed);
306 return true;
307 }
308
309 void on_collapse_change(bContext &C, const bool is_collapsed) override
310 {
311 const bool is_expanded = !is_collapsed;
312
313 /* Let RNA handle the property change. This makes sure all the notifiers and DEG
314 * update calls are properly called. */
316 &armature_.id, &RNA_BoneCollection, &bone_collection_);
317 PropertyRNA *prop = RNA_struct_find_property(&bcoll_ptr, "is_expanded");
318
319 RNA_property_boolean_set(&bcoll_ptr, prop, is_expanded);
320 RNA_property_update(&C, &bcoll_ptr, prop);
321 }
322
323 bool supports_renaming() const override
324 {
325 return ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_);
326 }
327
328 bool rename(const bContext &C, StringRefNull new_name) override
329 {
330 /* Let RNA handle the renaming. This makes sure all the notifiers and DEG
331 * update calls are properly called. */
332 PointerRNA bcoll_ptr = rna_pointer();
333 PropertyRNA *prop = RNA_struct_find_property(&bcoll_ptr, "name");
334
335 RNA_property_string_set(&bcoll_ptr, prop, new_name.c_str());
336 RNA_property_update(&const_cast<bContext &>(C), &bcoll_ptr, prop);
337
338 ED_undo_push(&const_cast<bContext &>(C), "Rename Armature Bone Collection");
339 return true;
340 }
341
343 {
344 return bone_collection_.name;
345 }
346
347 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
348 {
349 /* Reject dragging linked (or otherwise uneditable) bone collections. */
350 if (!ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_)) {
351 return {};
352 }
353
354 BoneCollectionTreeView &tree_view = static_cast<BoneCollectionTreeView &>(get_tree_view());
355 return std::make_unique<BoneCollectionDragController>(tree_view, armature_, bcoll_index_);
356 }
357
358 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
359 {
360 ArmatureBoneCollection drop_bonecoll(&armature_, bcoll_index_);
361 /* For now, only support DropBehavior::Insert until there's code for actually reordering
362 * siblings. Currently only 'move to another parent' is implemented. */
363 return std::make_unique<BoneCollectionDropTarget>(
364 *this, DropBehavior::ReorderAndInsert, drop_bonecoll);
365 }
366
367 protected:
370 {
371 return RNA_pointer_create_discrete(&armature_.id, &RNA_BoneCollection, &bone_collection_);
372 }
373};
374
376
378{
379 build_bcolls_with_selected_bones();
380
381 for (int bcoll_index = 0; bcoll_index < armature_.collection_root_count; bcoll_index++) {
382 build_tree_node_recursive(*this, bcoll_index);
383 }
384}
385
386void BoneCollectionTreeView::build_tree_node_recursive(TreeViewItemContainer &parent,
387 const int bcoll_index)
388{
389 BoneCollection *bcoll = armature_.collection_array[bcoll_index];
390 const bool has_any_selected_bones = bcolls_with_selected_bones_.contains(bcoll);
391 BoneCollectionItem &bcoll_tree_item = parent.add_tree_item<BoneCollectionItem>(
392 armature_, bcoll_index, has_any_selected_bones);
393 for (int child_index = bcoll->child_index; child_index < bcoll->child_index + bcoll->child_count;
394 child_index++)
395 {
396 build_tree_node_recursive(bcoll_tree_item, child_index);
397 }
398}
399
401{
402 return notifier.data == ND_BONE_COLLECTION;
403}
404
405void BoneCollectionTreeView::build_bcolls_with_selected_bones()
406{
408
409 /* Armature Edit mode. */
410 if (armature_.edbo) {
412 if ((ebone->flag & BONE_SELECTED) == 0) {
413 continue;
414 }
415
416 LISTBASE_FOREACH (BoneCollectionReference *, ref, &ebone->bone_collections) {
418 }
419 }
420 return;
421 }
422
423 /* Any other mode. */
424 ANIM_armature_foreach_bone(&armature_.bonebase, [&](const Bone *bone) {
425 if ((bone->flag & BONE_SELECTED) == 0) {
426 return;
427 }
428
430 bcolls_with_selected_bones_.add(ref->bcoll);
431 }
432 });
433}
434
436 bArmature &armature,
437 const int bcoll_index)
438 : AbstractViewItemDragController(tree_view), drag_arm_bcoll_(&armature, bcoll_index)
439{
440}
441
446
448{
450 *drag_data = drag_arm_bcoll_;
451 return drag_data;
452}
453
455{
456 ANIM_armature_bonecoll_active_index_set(drag_arm_bcoll_.armature, drag_arm_bcoll_.bcoll_index);
457}
458
459} // namespace blender::ui::bonecollections
460
462{
463 using namespace blender;
464
465 bArmature *armature = ED_armature_context(C);
466 if (armature == nullptr) {
467 return;
468 }
469 BLI_assert(GS(armature->id.name) == ID_AR);
470
471 uiBlock *block = uiLayoutGetBlock(layout);
472
474 *block,
475 "Bone Collection Tree View",
476 std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*armature));
477 tree_view->set_context_menu_title("Bone Collection");
478 tree_view->set_default_rows(5);
479
480 ui::TreeViewBuilder::build_tree_view(*C, *tree_view, *layout);
481}
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)
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:99
#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)
void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C)
@ UI_BUT_INACTIVE
void UI_but_flag_enable(uiBut *but, int flag)
@ UI_ITEM_R_ICON_ONLY
void uiLayoutSetActive(uiLayout *layout, bool active)
uiBlock * uiLayoutGetBlock(uiLayout *layout)
void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout)
uiBut * uiItemL_ex(uiLayout *layout, blender::StringRef name, int icon, bool highlight, bool redalert)
#define ND_BONE_COLLECTION
Definition WM_types.hh:471
eWM_DragDataType
Definition WM_types.hh:1197
@ WM_DRAG_BONE_COLLECTION
Definition WM_types.hh:1221
#define NC_OBJECT
Definition WM_types.hh:376
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:620
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:691
void set_default_rows(int default_rows)
Definition tree_view.cc:139
void set_context_menu_title(const std::string &title)
static void build_tree_view(const bContext &C, AbstractTreeView &tree_view, uiLayout &layout, std::optional< StringRef > search_string={}, bool add_box=true)
Definition tree_view.cc:980
ItemT & add_tree_item(Args &&...args)
TreeViewItemDropTarget(AbstractTreeViewItem &view_item, DropBehavior behavior=DropBehavior::Insert)
Definition tree_view.cc:394
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(a)
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[66]
Definition DNA_ID.h:415
struct BoneCollection ** collection_array
ListBase * edbo
const wmDrag & drag_data
const DropLocation drop_location
void label(blender::StringRef name, int icon)
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:1327
void * poin
Definition WM_types.hh:1328
unsigned int data
Definition WM_types.hh:355
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
MenuType * WM_menutype_find(const StringRef idname, bool quiet)