Blender V5.0
outliner_utils.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2017 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cstring>
11
12#include "BLI_listbase.h"
13#include "BLI_utildefines.h"
14
15#include "DNA_action_types.h"
16#include "DNA_screen_types.h"
17#include "DNA_space_types.h"
18
19#include "RNA_access.hh"
20#include "RNA_prototypes.hh"
21
22#include "BKE_context.hh"
23#include "BKE_layer.hh"
24#include "BKE_object.hh"
25
26#include "ED_outliner.hh"
27#include "ED_screen.hh"
28
29#include "UI_interface.hh"
30#include "UI_view2d.hh"
31
32#include "outliner_intern.hh"
33#include "tree/tree_display.hh"
35
36namespace blender::ed::outliner {
37
38/* -------------------------------------------------------------------- */
41
43{
44 memset(tvc, 0, sizeof(*tvc));
45 /* Workspace. */
47
48 /* Scene level. */
49 tvc->scene = CTX_data_scene(C);
52
53 /* Objects. */
56 if (tvc->obact != nullptr) {
57 tvc->ob_edit = OBEDIT_FROM_OBACT(tvc->obact);
58
59 if ((tvc->obact->type == OB_ARMATURE) ||
60 /* This could be made into its own function. */
61 ((tvc->obact->type == OB_MESH) && tvc->obact->mode & OB_MODE_WEIGHT_PAINT))
62 {
64 }
65 }
66}
67
69
71 const ListBase *tree,
72 float view_co_y)
73{
74 LISTBASE_FOREACH (TreeElement *, te_iter, tree) {
75 if (view_co_y < (te_iter->ys + UI_UNIT_Y)) {
76 if (view_co_y >= te_iter->ys) {
77 /* co_y is inside this element */
78 return te_iter;
79 }
80
81 if (BLI_listbase_is_empty(&te_iter->subtree) ||
82 !TSELEM_OPEN(TREESTORE(te_iter), space_outliner))
83 {
84 /* No need for recursion. */
85 continue;
86 }
87
88 /* If the coordinate is lower than the next element, we can continue with that one and skip
89 * recursion too. */
90 const TreeElement *te_next = te_iter->next;
91 if (te_next && (view_co_y < (te_next->ys + UI_UNIT_Y))) {
92 continue;
93 }
94
95 /* co_y is lower than current element (but not lower than the next one), possibly inside
96 * children */
97 TreeElement *te_sub = outliner_find_item_at_y(space_outliner, &te_iter->subtree, view_co_y);
98 if (te_sub) {
99 return te_sub;
100 }
101 }
102 }
103
104 return nullptr;
105}
106
108 float view_co_x,
109 bool *r_is_merged_icon)
110{
111 TreeElement *child_te = static_cast<TreeElement *>(parent_te->subtree.first);
112
113 while (child_te) {
114 const bool over_element = (view_co_x > child_te->xs) && (view_co_x < child_te->xend);
115 if ((child_te->flag & TE_ICONROW) && over_element) {
116 return child_te;
117 }
118 if ((child_te->flag & TE_ICONROW_MERGED) && over_element) {
119 if (r_is_merged_icon) {
120 *r_is_merged_icon = true;
121 }
122 return child_te;
123 }
124
126 child_te, view_co_x, r_is_merged_icon);
127 if (te != child_te) {
128 return te;
129 }
130
131 child_te = child_te->next;
132 }
133
134 /* return parent if no child is hovered */
135 return (TreeElement *)parent_te;
136}
137
139 TreeElement *parent_te,
140 float view_co_x,
141 bool *r_is_merged_icon,
142 bool *r_is_over_icon)
143{
144 TreeStoreElem *parent_tselem = TREESTORE(parent_te);
145 TreeElement *te = parent_te;
146
147 /* If parent_te is opened, or it is a ViewLayer, it doesn't show children in row. */
148 if (!TSELEM_OPEN(parent_tselem, space_outliner) && parent_tselem->type != TSE_R_LAYER) {
149 te = outliner_find_item_at_x_in_row_recursive(parent_te, view_co_x, r_is_merged_icon);
150 }
151
152 if ((te != parent_te) || outliner_item_is_co_over_icon(parent_te, view_co_x)) {
153 *r_is_over_icon = true;
154 }
155
156 return te;
157}
158
160{
161 LISTBASE_FOREACH (TreeElement *, te, lb) {
162 if (te->store_elem == store_elem) {
163 return te;
164 }
165 TreeElement *tes = outliner_find_tree_element(&te->subtree, store_elem);
166 if (tes) {
167 return tes;
168 }
169 }
170 return nullptr;
171}
172
174 TreeElement *parent_te,
175 const TreeElement *child_te)
176{
177 LISTBASE_FOREACH (TreeElement *, te, lb) {
178 if (te == child_te) {
179 return parent_te;
180 }
181
182 TreeElement *find_te = outliner_find_parent_element(&te->subtree, te, child_te);
183 if (find_te) {
184 return find_te;
185 }
186 }
187 return nullptr;
188}
189
190TreeElement *outliner_find_id(SpaceOutliner *space_outliner, ListBase *lb, const ID *id)
191{
192 LISTBASE_FOREACH (TreeElement *, te, lb) {
193 TreeStoreElem *tselem = TREESTORE(te);
194 if (tselem->type == TSE_SOME_ID) {
195 if (tselem->id == id) {
196 return te;
197 }
198 }
199 else if (tselem->type == TSE_RNA_STRUCT) {
200 /* No ID, so check if entry is RNA-struct, and if that RNA-struct is an ID datablock we are
201 * good. */
203 if (te_rna_struct) {
204 const PointerRNA &ptr = te_rna_struct->get_pointer_rna();
205 if (RNA_struct_is_ID(ptr.type)) {
206 if (static_cast<ID *>(ptr.data) == id) {
207 return te;
208 }
209 }
210 }
211 }
212
213 TreeElement *tes = outliner_find_id(space_outliner, &te->subtree, id);
214 if (tes) {
215 return tes;
216 }
217 }
218 return nullptr;
219}
220
222{
223 LISTBASE_FOREACH (TreeElement *, te, lb) {
224 if (te->directdata == pchan) {
225 return te;
226 }
227
228 TreeStoreElem *tselem = TREESTORE(te);
229 if (ELEM(tselem->type, TSE_POSE_BASE, TSE_POSE_CHANNEL)) {
230 TreeElement *tes = outliner_find_posechannel(&te->subtree, pchan);
231 if (tes) {
232 return tes;
233 }
234 }
235 }
236 return nullptr;
237}
238
240{
241 LISTBASE_FOREACH (TreeElement *, te, lb) {
242 if (te->directdata == ebone) {
243 return te;
244 }
245
246 TreeStoreElem *tselem = TREESTORE(te);
247 if (ELEM(tselem->type, TSE_SOME_ID, TSE_EBONE)) {
248 TreeElement *tes = outliner_find_editbone(&te->subtree, ebone);
249 if (tes) {
250 return tes;
251 }
252 }
253 }
254 return nullptr;
255}
256
258{
259 TreeStoreElem *tselem;
260 te = te->parent;
261
262 while (te) {
263 tselem = TREESTORE(te);
264 if ((tselem->type == TSE_SOME_ID) && (te->idcode == idcode)) {
265 return te;
266 }
267 te = te->parent;
268 }
269 return nullptr;
270}
271
273{
274 TreeElement *search_te;
275 TreeStoreElem *tselem;
276
277 search_te = outliner_search_back_te(te, idcode);
278 if (search_te) {
279 tselem = TREESTORE(search_te);
280 return tselem->id;
281 }
282 return nullptr;
283}
284
285bool outliner_tree_traverse(const SpaceOutliner *space_outliner,
286 ListBase *tree,
287 int filter_te_flag,
288 int filter_tselem_flag,
290 void *customdata)
291{
292 for (TreeElement *te = static_cast<TreeElement *>(tree->first), *te_next; te; te = te_next) {
294 /* in case te is freed in callback */
295 TreeStoreElem *tselem = TREESTORE(te);
296 ListBase subtree = te->subtree;
297 te_next = te->next;
298
299 if (filter_te_flag && (te->flag & filter_te_flag) == 0) {
300 /* skip */
301 }
302 else if (filter_tselem_flag && (tselem->flag & filter_tselem_flag) == 0) {
303 /* skip */
304 }
305 else {
306 func_retval = func(te, customdata);
307 }
308 /* Don't access te or tselem from now on! Might've been freed... */
309
310 if (func_retval == TRAVERSE_BREAK) {
311 return false;
312 }
313
314 if (func_retval == TRAVERSE_SKIP_CHILDS) {
315 /* skip */
316 }
317 else if (!outliner_tree_traverse(
318 space_outliner, &subtree, filter_te_flag, filter_tselem_flag, func, customdata))
319 {
320 return false;
321 }
322 }
323
324 return true;
325}
326
327float outliner_right_columns_width(const SpaceOutliner *space_outliner)
328{
329 int num_columns = 0;
330
331 switch (space_outliner->outlinevis) {
332 case SO_DATA_API:
333 case SO_SEQUENCE:
334 case SO_LIBRARIES:
335 return 0.0f;
339 num_columns = OL_RNA_COL_SIZEX / UI_UNIT_X;
340 break;
342 num_columns = 1;
343 break;
344 }
345 break;
346 case SO_ID_ORPHANS:
347 num_columns = 3;
348 break;
349 case SO_VIEW_LAYER:
350 if (space_outliner->show_restrict_flags & SO_RESTRICT_ENABLE) {
351 num_columns++;
352 }
353 if (space_outliner->show_restrict_flags & SO_RESTRICT_HOLDOUT) {
354 num_columns++;
355 }
356 if (space_outliner->show_restrict_flags & SO_RESTRICT_INDIRECT_ONLY) {
357 num_columns++;
358 }
360 case SO_SCENES:
361 if (space_outliner->show_restrict_flags & SO_RESTRICT_SELECT) {
362 num_columns++;
363 }
364 if (space_outliner->show_restrict_flags & SO_RESTRICT_HIDE) {
365 num_columns++;
366 }
367 if (space_outliner->show_restrict_flags & SO_RESTRICT_VIEWPORT) {
368 num_columns++;
369 }
370 if (space_outliner->show_restrict_flags & SO_RESTRICT_RENDER) {
371 num_columns++;
372 }
373 break;
374 }
375 return (num_columns * UI_UNIT_X + V2D_SCROLL_WIDTH);
376}
377
379{
380 LISTBASE_FOREACH (TreeElement *, te, lb) {
381 if ((TREESTORE(te)->flag & flag) == flag) {
382 return te;
383 }
384 TreeElement *active_element = outliner_find_element_with_flag(&te->subtree, flag);
385 if (active_element) {
386 return active_element;
387 }
388 }
389 return nullptr;
390}
391
393{
394 TreeStoreElem *tselem;
395
396 while (te->parent) {
397 tselem = TREESTORE(te->parent);
398
399 if (tselem->flag & TSE_CLOSED) {
400 return false;
401 }
402 te = te->parent;
403 }
404
405 return true;
406}
407
409{
410 return ((te->ys + UI_UNIT_Y) >= v2d->cur.ymin) && (te->ys <= v2d->cur.ymax);
411}
412
413bool outliner_item_is_co_over_name_icons(const TreeElement *te, float view_co_x)
414{
415 /* Special case: count area left of Scene Collection as empty space */
416 bool outside_left = (TREESTORE(te)->type == TSE_VIEW_COLLECTION_BASE) ?
417 (view_co_x > te->xs + UI_UNIT_X) :
418 (view_co_x > te->xs);
419
420 return outside_left && (view_co_x < te->xend);
421}
422
423bool outliner_item_is_co_over_icon(const TreeElement *te, float view_co_x)
424{
425 return (view_co_x > (te->xs + UI_UNIT_X)) && (view_co_x < (te->xs + UI_UNIT_X * 2));
426}
427
428bool outliner_item_is_co_over_name(const TreeElement *te, float view_co_x)
429{
430 return (view_co_x > (te->xs + UI_UNIT_X * 2)) && (view_co_x < te->xend);
431}
432
434{
435 return (view_co_x > te->xs) && (view_co_x < te->xs + UI_UNIT_X);
436}
437
438void outliner_scroll_view(SpaceOutliner *space_outliner, ARegion *region, int delta_y)
439{
440 int tree_width, tree_height;
441 outliner_tree_dimensions(space_outliner, &tree_width, &tree_height);
442 int y_min = std::min(int(region->v2d.cur.ymin), -tree_height);
443
444 region->v2d.cur.ymax += delta_y;
445 region->v2d.cur.ymin += delta_y;
446
447 /* Adjust view if delta placed view outside total area */
448 int offset;
449 if (region->v2d.cur.ymax > -UI_UNIT_Y) {
450 offset = region->v2d.cur.ymax;
451 region->v2d.cur.ymax -= offset;
452 region->v2d.cur.ymin -= offset;
453 }
454 else if (region->v2d.cur.ymin < y_min) {
455 offset = y_min - region->v2d.cur.ymin;
456 region->v2d.cur.ymax += offset;
457 region->v2d.cur.ymin += offset;
458 }
459}
460
462 ARegion *region)
463{
464 /* Avoid rebuild if possible. */
465 if (space_outliner->runtime->tree_display->is_lazy_built()) {
466 ED_region_tag_redraw(region);
467 }
468 else {
470 }
471}
472
473} // namespace blender::ed::outliner
474
475using namespace blender::ed::outliner;
476
478{
479 ARegion *region = CTX_wm_region(C);
480 const Scene *scene = CTX_data_scene(C);
481 ViewLayer *view_layer = CTX_data_view_layer(C);
482 SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
483 TreeElement *te;
484 Base *base = nullptr;
485 float view_mval[2];
486
487 UI_view2d_region_to_view(&region->v2d, mval[0], mval[1], &view_mval[0], &view_mval[1]);
488
489 te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]);
490 if (te) {
491 TreeStoreElem *tselem = TREESTORE(te);
492 if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) {
493 Object *ob = (Object *)tselem->id;
494 BKE_view_layer_synced_ensure(scene, view_layer);
495 base = (te->directdata) ? (Base *)te->directdata : BKE_view_layer_base_find(view_layer, ob);
496 }
497 }
498
499 return base;
500}
501
503{
504 ARegion *region = CTX_wm_region(C);
505 SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
506
507 float view_mval[2];
508 UI_view2d_region_to_view(&region->v2d, mval[0], mval[1], &view_mval[0], &view_mval[1]);
509
510 TreeElement *te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]);
511 if (!te) {
512 return false;
513 }
514
515 bool success = true;
516 TreeStoreElem *tselem = TREESTORE(te);
517 switch (tselem->type) {
518 case TSE_BONE: {
519 Bone *bone = (Bone *)te->directdata;
520 *r_ptr = RNA_pointer_create_discrete(tselem->id, &RNA_Bone, bone);
521 break;
522 }
523 case TSE_POSE_CHANNEL: {
524 bPoseChannel *pchan = (bPoseChannel *)te->directdata;
525 *r_ptr = RNA_pointer_create_discrete(tselem->id, &RNA_PoseBone, pchan);
526 break;
527 }
528 case TSE_EBONE: {
529 EditBone *bone = (EditBone *)te->directdata;
530 *r_ptr = RNA_pointer_create_discrete(tselem->id, &RNA_EditBone, bone);
531 break;
532 }
533
534 default:
535 success = false;
536 break;
537 }
538 return success;
539}
WorkSpace * CTX_wm_workspace(const bContext *C)
LayerCollection * CTX_data_layer_collection(const bContext *C)
SpaceOutliner * CTX_wm_space_outliner(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
void BKE_view_layer_synced_ensure(const Scene *scene, ViewLayer *view_layer)
Object * BKE_view_layer_active_object_get(const ViewLayer *view_layer)
Base * BKE_view_layer_base_find(ViewLayer *view_layer, Object *ob)
General operations, lookup, etc. for blender objects.
Object * BKE_object_pose_armature_get(Object *ob)
#define ATTR_FALLTHROUGH
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
#define ELEM(...)
@ ID_OB
@ OB_MODE_WEIGHT_PAINT
@ OB_ARMATURE
@ OB_MESH
@ TSE_POSE_CHANNEL
@ TSE_VIEW_COLLECTION_BASE
@ TSE_EBONE
@ TSE_BONE
@ TSE_SOME_ID
@ TSE_R_LAYER
@ TSE_RNA_STRUCT
@ TSE_POSE_BASE
@ TSE_CLOSED
#define OBEDIT_FROM_OBACT(ob)
eSpaceOutliner_LibOverrideViewMode
@ SO_LIB_OVERRIDE_VIEW_HIERARCHIES
@ SO_LIB_OVERRIDE_VIEW_PROPERTIES
@ SO_RESTRICT_HIDE
@ SO_RESTRICT_RENDER
@ SO_RESTRICT_INDIRECT_ONLY
@ SO_RESTRICT_VIEWPORT
@ SO_RESTRICT_ENABLE
@ SO_RESTRICT_HOLDOUT
@ SO_RESTRICT_SELECT
@ SO_OVERRIDES_LIBRARY
@ SO_SEQUENCE
@ SO_DATA_API
@ SO_LIBRARIES
@ SO_VIEW_LAYER
@ SO_SCENES
@ SO_ID_ORPHANS
void ED_region_tag_redraw_no_rebuild(ARegion *region)
Definition area.cc:638
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
#define C
Definition RandGen.cpp:29
#define UI_UNIT_Y
#define UI_UNIT_X
#define V2D_SCROLL_WIDTH
Definition UI_view2d.hh:54
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1668
KDTree_3d * tree
bool outliner_item_is_co_within_close_toggle(const TreeElement *te, float view_co_x)
TreeTraversalAction(*)(TreeElement *te, void *customdata) TreeTraversalFunc
bool outliner_item_is_co_over_icon(const TreeElement *te, float view_co_x)
void outliner_tag_redraw_avoid_rebuild_on_open_change(const SpaceOutliner *space_outliner, ARegion *region)
TreeElement * outliner_find_posechannel(ListBase *lb, const bPoseChannel *pchan)
TreeElement * outliner_find_item_at_x_in_row(const SpaceOutliner *space_outliner, TreeElement *parent_te, float view_co_x, bool *r_is_merged_icon, bool *r_is_over_icon)
TreeElement * outliner_find_id(SpaceOutliner *space_outliner, ListBase *lb, const ID *id)
static TreeElement * outliner_find_item_at_x_in_row_recursive(const TreeElement *parent_te, float view_co_x, bool *r_is_merged_icon)
float outliner_right_columns_width(const SpaceOutliner *space_outliner)
TreeElement * outliner_search_back_te(TreeElement *te, short idcode)
TreeElement * outliner_find_item_at_y(const SpaceOutliner *space_outliner, const ListBase *tree, float view_co_y)
void outliner_viewcontext_init(const bContext *C, TreeViewContext *tvc)
bool outliner_is_element_in_view(const TreeElement *te, const View2D *v2d)
TreeElement * outliner_find_editbone(ListBase *lb, const EditBone *ebone)
void outliner_scroll_view(SpaceOutliner *space_outliner, ARegion *region, int delta_y)
bool outliner_is_element_visible(const TreeElement *te)
bool outliner_tree_traverse(const SpaceOutliner *space_outliner, ListBase *tree, int filter_te_flag, int filter_tselem_flag, TreeTraversalFunc func, void *customdata)
TreeElementT * tree_element_cast(const TreeElement *te)
ID * outliner_search_back(TreeElement *te, short idcode)
TreeElement * outliner_find_tree_element(ListBase *lb, const TreeStoreElem *store_elem)
TreeElement * outliner_find_parent_element(ListBase *lb, TreeElement *parent_te, const TreeElement *child_te)
bool outliner_item_is_co_over_name(const TreeElement *te, float view_co_x)
bool outliner_item_is_co_over_name_icons(const TreeElement *te, float view_co_x)
TreeElement * outliner_find_element_with_flag(const ListBase *lb, short flag)
void outliner_tree_dimensions(SpaceOutliner *space_outliner, int *r_width, int *r_height)
#define TREESTORE(a)
#define OL_RNA_COL_SIZEX
#define TSELEM_OPEN(telm, sv)
Base * ED_outliner_give_base_under_cursor(bContext *C, const int mval[2])
bool ED_outliner_give_rna_under_cursor(bContext *C, const int mval[2], PointerRNA *r_ptr)
bool RNA_struct_is_ID(const StructRNA *type)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
Definition DNA_ID.h:414
void * first
short lib_override_view_mode
SpaceOutliner_Runtime * runtime
std::unique_ptr< AbstractTreeDisplay > tree_display
float ymax
float ymin
Establish and manage Outliner trees for different display modes.
PointerRNA * ptr
Definition wm_files.cc:4238
uint8_t flag
Definition wm_window.cc:145