Blender V4.3
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
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
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(TIP_("Move {} into {}"), drag_name, drop_name);
146 return fmt::format(TIP_("Move {} above {}"), drag_name, drop_name);
148 return fmt::format(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 = uiLayoutRow(&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 uiItemL(sub, "", icon);
242 }
243
244 /* Visibility eye icon. */
245 {
246 const bool is_solo_active = armature_.flag & ARM_BCOLL_SOLO_ACTIVE;
247 uiLayout *visibility_sub = uiLayoutRow(sub, 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 uiItemR(visibility_sub, &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 uiItemR(sub, &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. */
282 PointerRNA bcolls_ptr = RNA_pointer_create(&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(&const_cast<bContext &>(C), &bcolls_ptr, prop);
287
288 ED_undo_push(&const_cast<bContext &>(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. */
314 PointerRNA bcoll_ptr = RNA_pointer_create(
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 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
347 {
348 /* Reject dragging linked (or otherwise uneditable) bone collections. */
349 if (!ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_)) {
350 return {};
351 }
352
353 BoneCollectionTreeView &tree_view = static_cast<BoneCollectionTreeView &>(get_tree_view());
354 return std::make_unique<BoneCollectionDragController>(tree_view, armature_, bcoll_index_);
355 }
356
357 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
358 {
359 ArmatureBoneCollection drop_bonecoll(&armature_, bcoll_index_);
360 /* For now, only support DropBehavior::Insert until there's code for actually reordering
361 * siblings. Currently only 'move to another parent' is implemented. */
362 return std::make_unique<BoneCollectionDropTarget>(
363 *this, DropBehavior::ReorderAndInsert, drop_bonecoll);
364 }
365
366 protected:
369 {
370 return RNA_pointer_create(&armature_.id, &RNA_BoneCollection, &bone_collection_);
371 }
372};
373
375
377{
378 build_bcolls_with_selected_bones();
379
380 for (int bcoll_index = 0; bcoll_index < armature_.collection_root_count; bcoll_index++) {
381 build_tree_node_recursive(*this, bcoll_index);
382 }
383}
384
385void BoneCollectionTreeView::build_tree_node_recursive(TreeViewItemContainer &parent,
386 const int bcoll_index)
387{
388 BoneCollection *bcoll = armature_.collection_array[bcoll_index];
389 const bool has_any_selected_bones = bcolls_with_selected_bones_.contains(bcoll);
390 BoneCollectionItem &bcoll_tree_item = parent.add_tree_item<BoneCollectionItem>(
391 armature_, bcoll_index, has_any_selected_bones);
392 for (int child_index = bcoll->child_index; child_index < bcoll->child_index + bcoll->child_count;
393 child_index++)
394 {
395 build_tree_node_recursive(bcoll_tree_item, child_index);
396 }
397}
398
400{
401 return notifier.data == ND_BONE_COLLECTION;
402}
403
404void BoneCollectionTreeView::build_bcolls_with_selected_bones()
405{
407
408 /* Armature Edit mode. */
409 if (armature_.edbo) {
411 if ((ebone->flag & BONE_SELECTED) == 0) {
412 continue;
413 }
414
415 LISTBASE_FOREACH (BoneCollectionReference *, ref, &ebone->bone_collections) {
417 }
418 }
419 return;
420 }
421
422 /* Any other mode. */
424 if ((bone->flag & BONE_SELECTED) == 0) {
425 return;
426 }
427
429 bcolls_with_selected_bones_.add(ref->bcoll);
430 }
431 });
432}
433
434BoneCollectionDragController::BoneCollectionDragController(BoneCollectionTreeView &tree_view,
435 bArmature &armature,
436 const int bcoll_index)
437 : AbstractViewItemDragController(tree_view), drag_arm_bcoll_(&armature, bcoll_index)
438{
439}
440
445
447{
448 ArmatureBoneCollection *drag_data = MEM_cnew<ArmatureBoneCollection>(__func__);
449 *drag_data = drag_arm_bcoll_;
450 return drag_data;
451}
452
457
458} // namespace blender::ui::bonecollections
459
461{
462 using namespace blender;
463
464 bArmature *armature = ED_armature_context(C);
465 if (armature == nullptr) {
466 return;
467 }
468 BLI_assert(GS(armature->id.name) == ID_AR);
469
470 uiBlock *block = uiLayoutGetBlock(layout);
471
473 *block,
474 "Bone Collection Tree View",
475 std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*armature));
476 tree_view->set_context_menu_title("Bone Collection");
477 tree_view->set_default_rows(3);
478
479 ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
480}
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:50
#define LISTBASE_FOREACH(type, var, list)
#define TIP_(msgid)
@ ID_AR
@ BONE_SELECTED
@ ARM_BCOLL_SOLO_ACTIVE
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:104
blender::ui::AbstractGridView * UI_block_add_view(uiBlock &block, blender::StringRef idname, std::unique_ptr< blender::ui::AbstractGridView > grid_view)
void uiLayoutSetActive(uiLayout *layout, bool active)
void uiItemL(uiLayout *layout, const char *name, int icon)
uiBlock * uiLayoutGetBlock(uiLayout *layout)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C)
void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout)
uiBut * uiItemL_ex(uiLayout *layout, const char *name, int icon, bool highlight, bool redalert)
void UI_but_flag_enable(uiBut *but, int flag)
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
@ UI_ITEM_R_ICON_ONLY
@ UI_BUT_INACTIVE
#define ND_BONE_COLLECTION
Definition WM_types.hh:441
eWM_DragDataType
Definition WM_types.hh:1152
@ WM_DRAG_BONE_COLLECTION
Definition WM_types.hh:1176
#define NC_OBJECT
Definition WM_types.hh:346
bArmature * ED_armature_context(const bContext *C)
bool contains(const Key &key) const
Definition BLI_set.hh:291
bool add(const Key &key)
Definition BLI_set.hh:248
void clear()
Definition BLI_set.hh:532
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:565
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:629
void set_default_rows(int default_rows)
Definition tree_view.cc:129
void set_context_menu_title(const std::string &title)
static void build_tree_view(AbstractTreeView &tree_view, uiLayout &layout, std::optional< StringRef > search_string={}, bool add_box=true)
Definition tree_view.cc:918
ItemT & add_tree_item(Args &&...args)
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)
Definition iris.cc:202
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(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:425
struct BoneCollection ** collection_array
ListBase * edbo
struct bArmature_Runtime runtime
const wmDrag & drag_data
const DropLocation drop_location
eWM_DragDataType type
Definition WM_types.hh:1282
void * poin
Definition WM_types.hh:1283
unsigned int data
Definition WM_types.hh:325
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
MenuType * WM_menutype_find(const char *idname, bool quiet)