Blender V5.0
tree_view.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "DNA_userdef_types.h"
11
12#include "BKE_context.hh"
13
14#include "BLT_translation.hh"
15
16#include "GPU_immediate.hh"
17#include "GPU_state.hh"
18
19#include "interface_intern.hh"
20
22#include "UI_view2d.hh"
23
24#include "WM_api.hh"
25#include "WM_types.hh"
26
27#include "BLI_listbase.h"
28#include "BLI_math_base.h"
30#include "BLI_string.h"
31
32#include "UI_tree_view.hh"
33
34namespace blender::ui {
35
36#define UI_TREEVIEW_INDENT short(0.7f * UI_UNIT_X)
37#define MIN_ROWS 4
38
40{
41 return UI_UNIT_Y;
42}
44{
45 const uiStyle *style = UI_style_get_dpi();
46 return unpadded_item_height() + style->buttonspacey;
47}
48
49/* ---------------------------------------------------------------------- */
50
52 std::unique_ptr<AbstractTreeViewItem> item)
53{
54 children_.append(std::move(item));
55
56 /* The first item that will be added to the root sets this. */
57 if (root_ == nullptr) {
58 root_ = this;
59 }
60 AbstractTreeView &tree_view = static_cast<AbstractTreeView &>(*root_);
61 AbstractTreeViewItem &added_item = *children_.last();
62 added_item.root_ = root_;
63 tree_view.register_item(added_item);
64
65 if (root_ != this) {
66 /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely
67 * nice to static_cast this, but well... */
68 added_item.parent_ = static_cast<AbstractTreeViewItem *>(this);
69 }
70
71 return added_item;
72}
73
75{
76 for (const auto &child : children_) {
77 bool skip = false;
78 if (bool(options & IterOptions::SkipFiltered) && !child->is_filtered_visible()) {
79 skip = true;
80 }
81
82 if (!skip) {
83 iter_fn(*child);
84 }
85
86 if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) {
87 continue;
88 }
89
90 child->foreach_item_recursive(iter_fn, options);
91 }
92}
93
95{
96 for (ui::AbstractTreeViewItem *item = parent_; item; item = item->parent_) {
97 iter_fn(*item);
98 }
99}
100
101/* ---------------------------------------------------------------------- */
102
103void AbstractTreeView::foreach_view_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const
104{
105 /* Implementation for the base class virtual function. More specialized iterators below. */
106
107 this->foreach_item_recursive(iter_fn);
108}
109
114
116{
117 for (const auto &child : children_) {
118 iter_fn(*child);
119 }
120}
121
123{
124 AbstractTreeViewItem *hovered_item = nullptr;
126 [&](AbstractTreeViewItem &item) {
127 if (hovered_item) {
128 return;
129 }
130
131 std::optional<rctf> win_rect = item.get_win_rect(region);
132 if (win_rect && BLI_rctf_isect_y(&*win_rect, xy[1])) {
133 hovered_item = &item;
134 }
135 },
137
138 return hovered_item;
139}
140
142{
143 BLI_assert_msg(default_rows >= MIN_ROWS,
144 "Default value is smaller than the minimum rows. Limit is required to prevent "
145 "resizing below specific height.");
146 custom_height_ = std::make_unique<int>(default_rows * padded_item_height());
147}
148
149std::optional<uiViewState> AbstractTreeView::persistent_state() const
150{
152
153 SET_FLAG_FROM_TEST(state.flag, *show_display_options_, UI_VIEW_SHOW_FILTER_OPTIONS);
154 BLI_strncpy(state.search_string, search_string_.get(), sizeof(state.search_string));
155
156 if (!custom_height_ && !scroll_value_) {
157 return {};
158 }
159
160 if (custom_height_) {
161 state.custom_height = *custom_height_ * UI_INV_SCALE_FAC;
162 }
163 if (scroll_value_) {
164 state.scroll_offset = *scroll_value_;
165 }
166
167 return state;
168}
169
171{
172 if (state.custom_height) {
173 set_default_rows(std::max(
175 }
176 if (state.scroll_offset) {
177 scroll_value_ = std::make_shared<int>(state.scroll_offset);
178 }
179
180 *show_display_options_ = (state.flag & UI_VIEW_SHOW_FILTER_OPTIONS) != 0;
181 BLI_strncpy(search_string_.get(), state.search_string, UI_MAX_NAME_STR);
182}
183
184int AbstractTreeView::count_visible_descendants(const AbstractTreeViewItem &parent) const
185{
186 if (parent.is_collapsed()) {
187 return 0;
188 }
189 int count = 0;
190 for (const auto &item : parent.children_) {
191 if (!item->is_filtered_visible()) {
192 continue;
193 }
194 count++;
195 count += count_visible_descendants(*item);
196 }
197
198 return count;
199}
200
201void AbstractTreeView::get_hierarchy_lines(const ARegion &region,
202 const TreeViewOrItem &parent,
203 const float aspect,
204 Vector<std::pair<int2, int2>> &lines,
205 int &visible_item_index) const
206{
207 const int scroll_ofs = scroll_value_ ? *scroll_value_ : 0;
208 const int max_visible_row_count = tot_visible_row_count().value_or(
209 std::numeric_limits<int>::max());
210
211 for (const auto &item : parent.children_) {
212 if (!item->is_filtered_visible()) {
213 continue;
214 }
215
216 const int item_index = visible_item_index;
217 visible_item_index++;
218
219 if (!item->is_collapsible() || item->is_collapsed()) {
220 continue;
221 }
222 if (item->children_.is_empty()) {
223 BLI_assert(item->is_always_collapsible_);
224 continue;
225 }
226
227 /* Draw a hierarchy line for the descendants of this item. */
228
229 const AbstractTreeViewItem *first_descendant = item->children_.first().get();
230 const int descendant_count = count_visible_descendants(*item);
231
232 const int first_descendant_index = item_index + 1;
233 const int last_descendant_index = item_index + descendant_count;
234
235 {
236 const bool line_ends_above_visible = last_descendant_index < scroll_ofs;
237 if (line_ends_above_visible) {
238 /* We won't recurse into the child items even though they are present (just scrolled out of
239 * view). Still update the index to be the first following item. */
240 visible_item_index = last_descendant_index + 1;
241 continue;
242 }
243
244 const bool line_starts_below_visible = first_descendant_index >
245 (scroll_ofs + long(max_visible_row_count));
246 /* Can return here even, following items won't be in view anymore. */
247 if (line_starts_below_visible) {
248 return;
249 }
250 }
251
252 const int x = ((first_descendant->indent_width() + uiLayoutListItemPaddingWidth() -
253 (0.5f * UI_ICON_SIZE) + U.pixelsize + UI_SCALE_FAC) /
254 aspect);
255 const int ymax = std::max(0, first_descendant_index - scroll_ofs) * padded_item_height() /
256 aspect;
257 const int ymin = std::min(max_visible_row_count, last_descendant_index + 1 - scroll_ofs) *
258 padded_item_height() / aspect;
259 lines.append(std::make_pair(int2(x, ymax), int2(x, ymin)));
260
261 this->get_hierarchy_lines(region, *item, aspect, lines, visible_item_index);
262 }
263}
264
266{
267 for (const std::unique_ptr<uiBut> &but : block.buttons) {
268 if (but->type != ButType::ViewItem) {
269 continue;
270 }
271 uiButViewItem *view_item_but = static_cast<uiButViewItem *>(but.get());
272 if (&view_item_but->view_item->get_view() == &view) {
273 return view_item_but;
274 }
275 }
276 return nullptr;
277}
278
279void AbstractTreeView::draw_hierarchy_lines(const ARegion &region, const uiBlock &block) const
280{
281 const float aspect = (region.v2d.flag & V2D_IS_INIT) ?
282 BLI_rctf_size_y(&region.v2d.cur) /
283 (BLI_rcti_size_y(&region.v2d.mask) + 1) :
284 1.0f;
285
286 uiButViewItem *first_item_but = find_first_view_item_but(block, *this);
287 if (!first_item_but) {
288 return;
289 }
290
292 int index = 0;
293 get_hierarchy_lines(region, *this, aspect, lines, index);
294 if (lines.is_empty()) {
295 return;
296 }
297
298 GPUVertFormat *format = immVertexFormat();
299 uint pos = GPU_vertformat_attr_add(format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
302
303 GPU_line_width(1.0f / aspect);
305
306 rcti first_item_but_pixel_rect;
307 ui_but_to_pixelrect(&first_item_but_pixel_rect, &region, &block, first_item_but);
308 int2 top_left{first_item_but_pixel_rect.xmin, first_item_but_pixel_rect.ymax};
309
310 for (const auto &line : lines) {
312 immVertex2f(pos, top_left.x + line.first.x, top_left.y - line.first.y);
313 immVertex2f(pos, top_left.x + line.second.x, top_left.y - line.second.y);
314 immEnd();
315 }
317
319}
320
321void AbstractTreeView::draw_overlays(const ARegion &region, const uiBlock &block) const
322{
323 this->draw_hierarchy_lines(region, block);
324}
325
326void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
327{
328 const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
329
330 custom_height_ = old_tree_view.custom_height_;
331 scroll_value_ = old_tree_view.scroll_value_;
332 search_string_ = old_tree_view.search_string_;
333 show_display_options_ = old_tree_view.show_display_options_;
334 update_children_from_old_recursive(*this, old_tree_view);
335}
336
337void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
338 const TreeViewOrItem &old_items)
339{
340 /* This map can't find the exact old item for a new item. However, it can drastically reduce the
341 * number of items that need to be checked. */
343 for (const auto &old_item : old_items.children_) {
344 old_children_by_label.add(old_item->label_, old_item.get());
345 }
346
347 for (const auto &new_item : new_items.children_) {
348 const Span<AbstractTreeViewItem *> possible_old_children = old_children_by_label.lookup(
349 new_item->label_);
350 AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item,
351 possible_old_children);
352 if (!matching_old_item) {
353 continue;
354 }
355
356 new_item->update_from_old(*matching_old_item);
357
358 /* Recurse into children of the matched item. */
359 update_children_from_old_recursive(*new_item, *matching_old_item);
360 }
361}
362
363AbstractTreeViewItem *AbstractTreeView::find_matching_child(
364 const AbstractTreeViewItem &lookup_item, const Span<AbstractTreeViewItem *> possible_items)
365{
366 for (auto *iter_item : possible_items) {
367 if (lookup_item.matches(*iter_item)) {
368 /* We have a matching item! */
369 return iter_item;
370 }
371 }
372
373 return nullptr;
374}
375
376std::optional<int> AbstractTreeView::tot_visible_row_count() const
377{
378 if (!custom_height_) {
379 return {};
380 }
381 const int calculate_rows = round_fl_to_int(float(*custom_height_) / padded_item_height());
382 /* Clamp value to prevent resizing below minimum number of rows. */
383 return math::max(MIN_ROWS, calculate_rows);
384}
385
387{
388 return custom_height_ && scroll_value_;
389}
390
392{
393 return this->tot_visible_row_count().value_or(0) >= last_tot_items_;
394}
395
397{
398 if (!supports_scrolling()) {
399 return;
400 }
401 /* Scroll value will be sanitized/clamped when drawing. */
402 *scroll_value_ += ((direction == ViewScrollDirection::UP) ? -1 : 1);
403}
404
405void AbstractTreeView::scroll_active_into_view()
406{
407 int index = 0;
408 const std::optional<int> visible_row_count = tot_visible_row_count();
409
410 if (!custom_height_) {
411 return;
412 }
413
414 if (!visible_row_count.has_value()) {
415 return;
416 }
417
418 if (scroll_active_into_view_on_draw_) {
419 if (!scroll_value_) {
420 scroll_value_ = std::make_unique<int>(0);
421 }
423 [&, this](AbstractTreeViewItem &item) {
424 if (item.is_active_) {
425 *scroll_value_ = std::max(0, index - *visible_row_count + 1);
426 return;
427 }
428 index++;
429 },
432 }
433}
434
435/* ---------------------------------------------------------------------- */
436
438 DropBehavior behavior)
439 : view_item_(view_item), behavior_(behavior)
440{
441}
442
444 const ARegion &region, const wmEvent &event) const
445{
447 return DropLocation::Into;
448 }
449
450 std::optional<rctf> win_rect = view_item_.get_win_rect(region);
451 if (!win_rect) {
453 return std::nullopt;
454 }
455 const float item_height = BLI_rctf_size_y(&*win_rect);
456
458
459 const int segment_count =
461 /* Divide into upper (insert before) and lower (insert after) half. */
462 2 :
463 /* Upper (insert before), middle (insert into) and lower (insert after) third. */
464 3;
465 const float segment_height = item_height / segment_count;
466
467 if (event.xy[1] < win_rect->ymin) {
468 return DropLocation::After;
469 }
470 if (event.xy[1] - win_rect->ymin > (item_height - segment_height)) {
472 }
473 if (event.xy[1] - win_rect->ymin <= segment_height) {
474 if (behavior_ == DropBehavior::ReorderAndInsert && view_item_.is_collapsible() &&
475 !view_item_.is_collapsed())
476 {
477 /* Special case: Dropping at the lower 3rd of an uncollapsed item should insert into it, not
478 * after. */
479 return DropLocation::Into;
480 }
481 return DropLocation::After;
482 }
483
485 return DropLocation::Into;
486}
487
488/* ---------------------------------------------------------------------- */
489
490void AbstractTreeViewItem::add_treerow_button(uiBlock &block)
491{
492 /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */
493 view_item_but_ = reinterpret_cast<uiButViewItem *>(uiDefBut(&block,
495 0,
496 "",
497 0,
498 0,
499 UI_UNIT_X * 10,
501 nullptr,
502 0,
503 0,
504 ""));
505
508}
509
510int AbstractTreeViewItem::indent_width() const
511{
512 return this->count_parents() * UI_TREEVIEW_INDENT;
513}
514
515void AbstractTreeViewItem::add_indent(uiLayout &row) const
516{
517 uiBlock *block = row.block();
518 uiLayout *subrow = &row.row(true);
519 subrow->fixed_size_set(true);
520
521 uiDefBut(block, ButType::Sepr, 0, "", 0, 0, this->indent_width(), 0, nullptr, 0.0, 0.0, "");
522
523 const bool is_flat_list = root_ && root_->is_flat_;
524 if (!is_flat_list && !this->is_collapsible()) {
525 /* Indent items without collapsing icon some more within their parent. Makes it clear that they
526 * are actually nested and not just a row at the same level without a chevron. */
527 uiDefBut(block, ButType::Sepr, 0, "", 0, 0, UI_TREEVIEW_INDENT, 0, nullptr, 0.0, 0.0, "");
528 }
529
530 /* Restore. */
531 block_layout_set_current(block, &row);
532}
533
534void AbstractTreeViewItem::collapse_chevron_click_fn(bContext *C,
535 void * /*but_arg1*/,
536 void * /*arg2*/)
537{
538 /* There's no data we could pass to this callback. It must be either the button itself or a
539 * consistent address to match buttons over redraws. So instead of passing it somehow, just
540 * lookup the hovered item via context here. */
541
542 const wmWindow *win = CTX_wm_window(C);
544 AbstractViewItem *hovered_abstract_item = UI_region_views_find_item_at(*region,
545 win->eventstate->xy);
546
547 auto *hovered_item = reinterpret_cast<AbstractTreeViewItem *>(hovered_abstract_item);
548 BLI_assert(hovered_item != nullptr);
549
550 hovered_item->toggle_collapsed_from_view(*C);
551 /* When collapsing an item with an active child, make this collapsed item active instead so the
552 * active item stays visible. */
553 if (hovered_item->has_active_child()) {
554 hovered_item->activate(*C);
555 }
556}
557
558void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const
559{
560 if (!this->is_collapsible()) {
561 return;
562 }
563
564 const BIFIconID icon = this->is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT;
565 uiBut *but = uiDefIconBut(
566 &block, ButType::ButToggle, 0, icon, 0, 0, UI_TREEVIEW_INDENT, UI_UNIT_Y, nullptr, 0, 0, "");
567 UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr);
569}
570
571void AbstractTreeViewItem::add_rename_button(uiLayout &row)
572{
573 uiBlock *block = row.block();
574 EmbossType previous_emboss = UI_block_emboss_get(block);
575
576 row.row(false);
577 /* Enable emboss for the text button. */
579
581
582 UI_block_emboss_set(block, previous_emboss);
583 block_layout_set_current(block, &row);
584}
585
586bool AbstractTreeViewItem::has_active_child() const
587{
588 bool found = false;
589 foreach_item_recursive([&found](const AbstractTreeViewItem &item) {
590 if (item.is_active()) {
591 found = true;
592 }
593 });
594
595 return found;
596}
597
599{
600 return true;
601}
602
607
609{
610 /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single()
611 * recognizes the item. (It only compares labels by default.) */
612 label_ = new_name;
613 return true;
614}
615
617{
619
620 const AbstractTreeViewItem &old_tree_item = dynamic_cast<const AbstractTreeViewItem &>(old);
621 is_open_ = old_tree_item.is_open_;
622}
623
625{
626 return label_ == other.label_;
627}
628
629std::unique_ptr<DropTargetInterface> AbstractTreeViewItem::create_item_drop_target()
630{
631 return this->create_drop_target();
632}
633
634std::unique_ptr<TreeViewItemDropTarget> AbstractTreeViewItem::create_drop_target()
635{
636 return nullptr;
637}
638
639std::optional<std::string> AbstractTreeViewItem::debug_name() const
640{
641 return label_;
642}
643
645{
646 return dynamic_cast<AbstractTreeView &>(get_view());
647}
648
649std::optional<rctf> AbstractTreeViewItem::get_win_rect(const ARegion &region) const
650{
651 uiButViewItem *item_but = view_item_button();
652 if (!item_but) {
653 return std::nullopt;
654 }
655
656 rctf win_rect;
657 ui_block_to_window_rctf(&region, item_but->block, &win_rect, &item_but->rect);
658
659 return win_rect;
660}
661
663{
664 int i = 0;
665 for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
666 i++;
667 }
668 return i;
669}
670
672{
674 /* Make sure the active item is always visible. */
676 return true;
677 }
678
679 return false;
680}
681
683{
684 BLI_assert_msg(get_tree_view().is_reconstructed(),
685 "State cannot be queried until reconstruction is completed");
687 "Hovered state cannot be queried before the tree row is being built");
688
689 /* The new layout hasn't finished construction yet, so the final state of the button is unknown.
690 * Get the matching button from the previous redraw instead. */
692 *view_item_but_->block, *this);
693 return old_item_but && (old_item_but->flag & UI_HOVER);
694}
695
697{
698 BLI_assert_msg(get_tree_view().is_reconstructed(),
699 "State cannot be queried until reconstruction is completed");
700 return this->is_collapsible() && !is_open_;
701}
702
704{
705 return this->set_collapsed(is_open_);
706}
707
714
715bool AbstractTreeViewItem::set_collapsed(const bool collapsed)
716{
717 if (!this->is_collapsible()) {
718 return false;
719 }
720 if (collapsed == !is_open_) {
721 return false;
722 }
723
724 is_open_ = !collapsed;
725 return true;
726}
727
728void AbstractTreeViewItem::on_collapse_change(bContext & /*C*/, const bool /*is_collapsed*/)
729{
730 /* Do nothing by default. */
731}
732
734{
735 return std::nullopt;
736}
737
739{
740 BLI_assert_msg(this->get_tree_view().is_reconstructed() == false,
741 "Default state should only be set while building the tree");
743 /* Set the open state. Note that this may be overridden later by #should_be_collapsed(). */
744 is_open_ = true;
745}
746
748{
749 BLI_assert_msg(get_tree_view().is_reconstructed(),
750 "State can't be queried until reconstruction is completed");
752 return true;
753 }
754 if (children_.is_empty()) {
755 return false;
756 }
757 return this->supports_collapsing();
758}
759
761{
762 const bool prev_active_state = is_active();
764
765 if (prev_active_state != is_active()) {
766 this->get_tree_view().scroll_active_into_view_on_draw_ = true;
767 }
768
769 const std::optional<bool> should_be_collapsed = this->should_be_collapsed();
770 if (should_be_collapsed.has_value()) {
771 /* This reflects an external state change and therefore shouldn't call #on_collapse_change().
772 */
773 this->set_collapsed(*should_be_collapsed);
774 }
775}
776
778{
779 for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
780 parent->set_collapsed(false);
781 }
782}
783
785{
786 const AbstractTreeViewItem &other_tree_item = dynamic_cast<const AbstractTreeViewItem &>(other);
787
788 if (!this->matches_single(other_tree_item)) {
789 return false;
790 }
791 if (this->count_parents() != other_tree_item.count_parents()) {
792 return false;
793 }
794
795 for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_;
796 parent && other_parent;
797 parent = parent->parent_, other_parent = other_parent->parent_)
798 {
799 if (!parent->matches_single(*other_parent)) {
800 return false;
801 }
802 }
803
804 return true;
805}
806
808{
809 BLI_assert(this->get_tree_view().search_string_ && this->get_tree_view().search_string_[0]);
810
813 item.is_filtered_visible_ = true;
814 item.set_collapsed(false);
815 });
816 }
817}
818
819/* ---------------------------------------------------------------------- */
820
821class TreeViewLayoutBuilder {
822 uiBlock &block_;
823 bool add_box_ = true;
824
825 friend TreeViewBuilder;
826
827 public:
828 void build_from_tree(AbstractTreeView &tree_view);
829 void build_row(AbstractTreeViewItem &item) const;
830
831 uiBlock &block() const;
832 uiLayout &current_layout() const;
833
834 private:
835 /* Created through #TreeViewBuilder (friend class). */
836 TreeViewLayoutBuilder(uiLayout &layout);
837};
838
839TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiLayout &layout) : block_(*layout.block()) {}
840
842{
843 int item_count = 0;
844 tree_view.foreach_item([&](AbstractTreeViewItem &) { item_count++; },
847 return item_count;
848}
849
851{
852 uiLayout &parent_layout = this->current_layout();
853 uiBlock *block = parent_layout.block();
854
855 uiLayout *col = nullptr;
856 if (add_box_) {
857 uiLayout *box = &parent_layout.box();
858 col = &box->column(true);
859 }
860 else {
861 col = &parent_layout.column(true);
862 }
863 /* Row for the tree-view and the scroll bar. */
864 uiLayout *row = &col->row(false);
865
866 const std::optional<int> visible_row_count = tree_view.tot_visible_row_count();
867 const int tot_items = count_visible_items(tree_view);
868 tree_view.last_tot_items_ = tot_items;
869
870 /* Column for the tree view. */
871 row->column(true);
872
873 if (tree_view.scroll_active_into_view_on_draw_) {
874 tree_view.scroll_active_into_view();
875 }
876
877 /* Clamp scroll-value to valid range. */
878 if (tree_view.scroll_value_ && visible_row_count) {
879 *tree_view.scroll_value_ = std::clamp(
880 *tree_view.scroll_value_, 0, tot_items - *visible_row_count);
881 }
882
883 const int first_visible_index = tree_view.scroll_value_ ? *tree_view.scroll_value_ : 0;
884 const int max_visible_index = visible_row_count ? first_visible_index + *visible_row_count - 1 :
885 std::numeric_limits<int>::max();
886 int index = 0;
887 tree_view.foreach_item(
888 [&, this](AbstractTreeViewItem &item) {
889 if ((index >= first_visible_index) && (index <= max_visible_index)) {
890 if (item.is_filtered_visible()) {
891 this->build_row(item);
892 }
893 }
894 index++;
895 },
897
898 if (tree_view.custom_height_) {
899
900 *tree_view.custom_height_ = visible_row_count.value_or(1) * padded_item_height();
901 if (!tree_view.scroll_value_) {
902 tree_view.scroll_value_ = std::make_unique<int>(0);
903 }
904
905 if (visible_row_count && (tot_items > *visible_row_count)) {
906 row->column(false);
907 uiBut *but = uiDefButI(block,
909 0,
910 "",
911 0,
912 0,
914 *tree_view.custom_height_,
915 tree_view.scroll_value_.get(),
916 0,
917 tot_items - *visible_row_count,
918 "");
919 uiButScrollBar *but_scroll = reinterpret_cast<uiButScrollBar *>(but);
920 but_scroll->visual_height = *visible_row_count;
921 }
922
924
925 /* Bottom */
926 uiLayout *bottom = &col->row(false);
928
929 int icon = *tree_view.show_display_options_ ? ICON_DISCLOSURE_TRI_DOWN :
930 ICON_DISCLOSURE_TRI_RIGHT;
933 1,
934 0,
935 icon,
936 0,
937 0,
938 UI_UNIT_X,
939 UI_UNIT_Y * 0.5,
940 tree_view.show_display_options_.get(),
941 0,
942 0,
943 TIP_(""));
946 bottom->column(false);
947
950 0,
951 ICON_GRIP,
952 0,
953 0,
954 UI_UNIT_X * 10,
955 UI_UNIT_Y * 0.5f,
956 tree_view.custom_height_.get(),
957 0,
958 0,
959 "");
960
961 if (*tree_view.show_display_options_) {
963 uiBut *but = uiDefBut(block,
965 1,
966 "",
967 0,
968 0,
970 UI_UNIT_Y,
971 tree_view.search_string_.get(),
972 0,
974 "");
977 ui_def_but_icon(but, ICON_VIEWZOOM, UI_HAS_ICON);
978 }
979 }
980
981 block_layout_set_current(block, &parent_layout);
982}
983
985{
986 uiBlock &block_ = block();
987
988 uiLayout &prev_layout = current_layout();
989
990 const int width = prev_layout.width();
991 if (width < int(40 * UI_SCALE_FAC)) {
992 return;
993 }
994
995 EmbossType previous_emboss = UI_block_emboss_get(&block_);
996
997 uiLayout *overlap = &prev_layout.overlap();
998
999 if (!item.is_interactive_) {
1000 overlap->active_set(false);
1001 }
1002
1003 uiLayout *row = &overlap->row(false);
1004 /* Enable emboss for mouse hover highlight. */
1006 /* Every item gets one! Other buttons can be overlapped on top. */
1007 item.add_treerow_button(block_);
1008
1009 /* After adding tree-row button (would disable hover highlighting). */
1011
1012 /* Add little margin to align actual contents vertically. */
1013 uiLayout *content_col = &overlap->column(true);
1014 const int margin_top = (padded_item_height() - unpadded_item_height()) / 2;
1015 if (margin_top > 0) {
1016 uiDefBut(&block_, ButType::Label, 0, "", 0, 0, UI_UNIT_X, margin_top, nullptr, 0, 0, "");
1017 }
1018 row = &content_col->row(true);
1019
1021 item.add_indent(*row);
1022 item.add_collapse_chevron(block_);
1023
1024 if (item.is_renaming()) {
1025 item.add_rename_button(*row);
1026 }
1027 else {
1028 item.build_row(*row);
1029 if (item.is_active_) {
1031 }
1032 }
1033
1035
1036 UI_block_emboss_set(&block_, previous_emboss);
1037 block_layout_set_current(&block_, &prev_layout);
1038}
1039
1041{
1042 return block_;
1043}
1044
1049
1050/* ---------------------------------------------------------------------- */
1051
1052void TreeViewBuilder::ensure_min_rows_items(AbstractTreeView &tree_view)
1053{
1054 const std::optional<int> visible_rows = tree_view.tot_visible_row_count();
1055 if (!visible_rows) {
1056 return;
1057 }
1058
1059 int tot_visible_items = 0;
1060 tree_view.foreach_item(
1061 [&tot_visible_items](AbstractTreeViewItem & /*item*/) { tot_visible_items++; },
1063
1064 if (tot_visible_items >= *visible_rows) {
1065 return;
1066 }
1067
1068 for (int i = 0; i < (*visible_rows - tot_visible_items); i++) {
1069 BasicTreeViewItem &new_item = tree_view.add_tree_item<BasicTreeViewItem>("");
1070 new_item.disable_interaction();
1071 }
1072}
1073
1075 AbstractTreeView &tree_view,
1076 uiLayout &layout,
1077 const bool add_box)
1078{
1079 uiBlock &block = *layout.block();
1080
1082 if (region) {
1083 ui_block_view_persistent_state_restore(*region, block, tree_view);
1084 }
1085
1086 tree_view.build_tree();
1087 tree_view.update_from_old(block);
1088 tree_view.change_state_delayed();
1089 {
1090 /* Setup search string to filter out elements with matching characters. */
1091 char string[UI_MAX_NAME_STR];
1092 BLI_strncpy_ensure_pad(string, tree_view.search_string_.get(), '*', sizeof(string));
1093 tree_view.filter(tree_view.search_string_ ? std::optional{string} : std::nullopt);
1094 }
1095 ensure_min_rows_items(tree_view);
1096
1097 /* Ensure the given layout is actually active. */
1098 block_layout_set_current(&block, &layout);
1099
1100 TreeViewLayoutBuilder builder(layout);
1101 builder.add_box_ = add_box;
1103 builder.build_from_tree(tree_view);
1105}
1106
1107/* ---------------------------------------------------------------------- */
1108
1110{
1111 label_ = label;
1112}
1113
1115{
1116 this->add_label(row);
1117}
1118
1120{
1121 const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override;
1122 layout.label(label, icon);
1123}
1124
1125void BasicTreeViewItem::on_activate(bContext &C)
1126{
1127 if (activate_fn_) {
1128 activate_fn_(C, *this);
1129 }
1130}
1131
1136
1138{
1139 is_active_fn_ = is_active_fn;
1140}
1141
1142std::optional<bool> BasicTreeViewItem::should_be_active() const
1143{
1144 if (is_active_fn_) {
1145 return is_active_fn_();
1146 }
1147 return std::nullopt;
1148}
1149
1150} // namespace blender::ui
ARegion * CTX_wm_region_popup(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
MINLINE int round_fl_to_int(float a)
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:198
bool BLI_rctf_isect_y(const rctf *rect, float y)
Definition rct.cc:104
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
char char * BLI_strncpy_ensure_pad(char *__restrict dst, const char *__restrict src, char pad, size_t dst_maxncpy) ATTR_NONNULL(1
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
unsigned int uint
#define SET_FLAG_FROM_TEST(value, test, flag)
#define ELEM(...)
#define TIP_(msgid)
@ UI_VIEW_SHOW_FILTER_OPTIONS
struct ARegion ARegion
#define UI_SCALE_FAC
#define UI_INV_SCALE_FAC
#define UI_ICON_SIZE
struct rcti rcti
@ V2D_IS_INIT
struct wmWindow wmWindow
int BIFIconID
Definition ED_asset.hh:28
static AppView * view
void immUniformThemeColorAlpha(int color_id, float a)
void immEnd()
void immUnbindProgram()
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
void immVertex2f(uint attr_id, float x, float y)
GPUVertFormat * immVertexFormat()
void immBegin(GPUPrimType, uint vertex_len)
@ GPU_PRIM_LINES
@ GPU_SHADER_3D_UNIFORM_COLOR
void GPU_line_width(float width)
Definition gpu_state.cc:166
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(GPUBlend blend)
Definition gpu_state.cc:42
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
#define C
Definition RandGen.cpp:29
void UI_but_func_set(uiBut *but, std::function< void(bContext &)> func)
uiBut * uiDefButI(uiBlock *block, ButType type, int retval, blender::StringRef str, int x, int y, short width, short height, int *poin, float min, float max, std::optional< blender::StringRef > tip)
void UI_but_flag_disable(uiBut *but, int flag)
#define UI_UNIT_Y
void UI_block_emboss_set(uiBlock *block, blender::ui::EmbossType emboss)
@ UI_BLOCK_LIST_ITEM
@ UI_BUT_UNDO
@ UI_BUT_VALUE_CLEAR
@ UI_BUT_TEXTEDIT_UPDATE
uiBut * uiDefIconBut(uiBlock *block, uiButTypeWithPointerType but_and_ptr_type, int retval, int icon, int x, int y, short width, short height, void *poin, float min, float max, std::optional< blender::StringRef > tip)
const uiStyle * UI_style_get_dpi()
void UI_block_flag_disable(uiBlock *block, int flag)
uiBut * uiDefIconButBitC(uiBlock *block, ButType type, int bit, int retval, int icon, int x, int y, short width, short height, char *poin, float min, float max, std::optional< blender::StringRef > tip)
uiBut * uiDefIconButI(uiBlock *block, ButType type, int retval, int icon, int x, int y, short width, short height, int *poin, float min, float max, std::optional< blender::StringRef > tip)
blender::ui::EmbossType UI_block_emboss_get(uiBlock *block)
#define UI_UNIT_X
void UI_block_flag_enable(uiBlock *block, int flag)
uiBut * uiDefBut(uiBlock *block, uiButTypeWithPointerType but_and_ptr_type, int retval, blender::StringRef str, int x, int y, short width, short height, void *poin, float min, float max, std::optional< blender::StringRef > tip)
void UI_but_flag_enable(uiBut *but, int flag)
blender::ui::AbstractViewItem * UI_region_views_find_item_at(const ARegion &region, const int xy[2])
int uiLayoutListItemPaddingWidth()
void uiLayoutListItemAddPadding(uiLayout *layout)
#define UI_MAX_NAME_STR
@ TH_TEXT
#define V2D_SCROLL_WIDTH
Definition UI_view2d.hh:54
#define U
bool is_empty() const
Span< Value > lookup(const Key &key) const
void add(const Key &key, const Value &value)
constexpr bool is_empty() const
Abstract base class for defining a customizable tree-view item.
std::optional< rctf > get_win_rect(const ARegion &region) const
Definition tree_view.cc:649
void update_from_old(const AbstractViewItem &old) override
Definition tree_view.cc:616
AbstractTreeView & get_tree_view() const
Definition tree_view.cc:644
std::unique_ptr< DropTargetInterface > create_item_drop_target() final
Definition tree_view.cc:629
StringRef get_rename_string() const override
Definition tree_view.cc:603
virtual void build_row(uiLayout &row)=0
bool matches(const AbstractViewItem &other) const override
Definition tree_view.cc:784
void toggle_collapsed_from_view(bContext &C)
Definition tree_view.cc:708
virtual std::unique_ptr< TreeViewItemDropTarget > create_drop_target()
Definition tree_view.cc:634
virtual void on_collapse_change(bContext &C, bool is_collapsed)
Definition tree_view.cc:728
virtual std::optional< bool > should_be_collapsed() const
Definition tree_view.cc:733
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:715
virtual bool matches_single(const AbstractTreeViewItem &other) const
Definition tree_view.cc:624
std::optional< std::string > debug_name() const override
Definition tree_view.cc:639
virtual bool supports_collapsing() const
Definition tree_view.cc:598
bool rename(const bContext &C, StringRefNull new_name) override
Definition tree_view.cc:608
AbstractTreeViewItem * find_hovered(const ARegion &region, const int2 &xy)
Definition tree_view.cc:122
void scroll(ViewScrollDirection direction) override
Definition tree_view.cc:396
void foreach_root_item(ItemIterFn iter_fn) const
Definition tree_view.cc:115
bool is_fully_visible() const override
Definition tree_view.cc:391
void persistent_state_apply(const uiViewState &state) override
Definition tree_view.cc:170
void draw_overlays(const ARegion &region, const uiBlock &block) const override
Definition tree_view.cc:321
void foreach_item(ItemIterFn iter_fn, IterOptions options=IterOptions::None) const
Definition tree_view.cc:110
std::optional< uiViewState > persistent_state() const override
Definition tree_view.cc:149
void set_default_rows(int default_rows)
Definition tree_view.cc:141
virtual void update_from_old(const AbstractViewItem &old)
void add_rename_button(uiBlock &block)
uiButViewItem * view_item_button() const
virtual std::optional< bool > should_be_active() const
virtual bool supports_scrolling() const
void update_from_old(uiBlock &new_block)
virtual void change_state_delayed()
void register_item(AbstractViewItem &item)
void filter(std::optional< StringRef > filter_str)
BasicTreeViewItem(StringRef label, BIFIconID icon=ICON_NONE)
void build_row(uiLayout &row) override
std::function< bool()> IsActiveFn
std::function< void(bContext &C, BasicTreeViewItem &new_active)> ActivateFn
void add_label(uiLayout &layout, StringRefNull label_override="")
void set_is_active_fn(IsActiveFn is_active_fn)
void set_on_activate_fn(ActivateFn fn)
static void build_tree_view(const bContext &C, AbstractTreeView &tree_view, uiLayout &layout, bool add_box=true)
ItemT & add_tree_item(Args &&...args)
AbstractTreeViewItem * parent_
TreeViewItemContainer * root_
void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options=IterOptions::None) const
Definition tree_view.cc:74
Vector< std::unique_ptr< AbstractTreeViewItem > > children_
void foreach_parent(ItemIterFn iter_fn) const
Definition tree_view.cc:94
FunctionRef< void(AbstractTreeViewItem &)> ItemIterFn
AbstractTreeViewItem & view_item_
TreeViewItemDropTarget(AbstractTreeViewItem &view_item, DropBehavior behavior=DropBehavior::Insert)
Definition tree_view.cc:437
std::optional< DropLocation > choose_drop_location(const ARegion &region, const wmEvent &event) const override
Definition tree_view.cc:443
void build_from_tree(AbstractTreeView &tree_view)
Definition tree_view.cc:850
void build_row(AbstractTreeViewItem &item) const
Definition tree_view.cc:984
CCL_NAMESPACE_BEGIN struct Options options
uint pos
uint col
#define long
void ui_def_but_icon(uiBut *but, const int icon, const int flag)
void ui_but_to_pixelrect(rcti *rect, const ARegion *region, const uiBlock *block, const uiBut *but)
void ui_block_to_window_rctf(const ARegion *region, const uiBlock *block, rctf *rct_dst, const rctf *rct_src)
Definition interface.cc:170
void ui_block_view_persistent_state_restore(const ARegion &region, const uiBlock &block, blender::ui::AbstractView &view)
uiButViewItem * ui_block_view_find_matching_view_item_but_in_old_block(const uiBlock &new_block, const blender::ui::AbstractViewItem &new_item)
void ui_layout_list_set_labels_active(uiLayout *layout)
@ UI_HOVER
@ UI_HAS_ICON
int count
format
static ulong state[N]
T max(const T &a, const T &b)
static int unpadded_item_height()
Definition tree_view.cc:39
static int count_visible_items(AbstractTreeView &tree_view)
Definition tree_view.cc:841
static uiButViewItem * find_first_view_item_but(const uiBlock &block, const AbstractTreeView &view)
Definition tree_view.cc:265
static int padded_item_height()
Definition tree_view.cc:43
TreeViewItemContainer TreeViewOrItem
void block_layout_set_current(uiBlock *block, uiLayout *layout)
VecBase< int32_t, 2 > int2
int ymax
int xmin
blender::Vector< std::unique_ptr< uiBut > > buttons
uiLayout * curlayout
blender::ui::AbstractViewItem * view_item
uiBlock * block
void fixed_size_set(bool fixed_size)
uiBlock * block() const
void label(blender::StringRef name, int icon)
uiLayout & column(bool align)
void active_set(bool active)
int width() const
uiLayout & row(bool align)
uiLayout & box()
void emboss_set(blender::ui::EmbossType emboss)
uiLayout & overlap()
short buttonspacey
int xy[2]
Definition WM_types.hh:761
struct wmEvent * eventstate
i
Definition text_draw.cc:230
#define MIN_ROWS
Definition tree_view.cc:37
#define UI_TREEVIEW_INDENT
Definition tree_view.cc:36
int xy[2]
Definition wm_draw.cc:178