Blender V4.3
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
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
18#include "interface_intern.hh"
19
20#include "UI_interface.hh"
21#include "UI_view2d.hh"
22
23#include "WM_api.hh"
24#include "WM_types.hh"
25
27
28#include "UI_tree_view.hh"
29
30namespace blender::ui {
31
32#define UI_TREEVIEW_INDENT short(0.7f * UI_UNIT_X)
33
35{
36 return UI_UNIT_Y;
37}
39{
40 const uiStyle *style = UI_style_get_dpi();
41 return unpadded_item_height() + style->buttonspacey;
42}
43
44/* ---------------------------------------------------------------------- */
45
47 std::unique_ptr<AbstractTreeViewItem> item)
48{
49 children_.append(std::move(item));
50
51 /* The first item that will be added to the root sets this. */
52 if (root_ == nullptr) {
53 root_ = this;
54 }
55 AbstractTreeView &tree_view = static_cast<AbstractTreeView &>(*root_);
56 AbstractTreeViewItem &added_item = *children_.last();
57 added_item.root_ = root_;
58 tree_view.register_item(added_item);
59
60 if (root_ != this) {
61 /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely
62 * nice to static_cast this, but well... */
63 added_item.parent_ = static_cast<AbstractTreeViewItem *>(this);
64 }
65
66 return added_item;
67}
68
70{
71 for (const auto &child : children_) {
72 bool skip = false;
73 if (bool(options & IterOptions::SkipFiltered) && !child->is_filtered_visible()) {
74 skip = true;
75 }
76
77 if (!skip) {
78 iter_fn(*child);
79 }
80
81 if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) {
82 continue;
83 }
84
85 child->foreach_item_recursive(iter_fn, options);
86 }
87}
88
90{
91 for (ui::AbstractTreeViewItem *item = parent_; item; item = item->parent_) {
92 iter_fn(*item);
93 }
94}
95
96/* ---------------------------------------------------------------------- */
97
98void AbstractTreeView::foreach_view_item(FunctionRef<void(AbstractViewItem &)> iter_fn) const
99{
100 /* Implementation for the base class virtual function. More specialized iterators below. */
101
102 this->foreach_item_recursive(iter_fn);
103}
104
109
111{
112 AbstractTreeViewItem *hovered_item = nullptr;
114 [&](AbstractTreeViewItem &item) {
115 if (hovered_item) {
116 return;
117 }
118
119 std::optional<rctf> win_rect = item.get_win_rect(region);
120 if (win_rect && BLI_rctf_isect_y(&*win_rect, xy[1])) {
121 hovered_item = &item;
122 }
123 },
125
126 return hovered_item;
127}
128
130{
131 custom_height_ = std::make_unique<int>(default_rows * padded_item_height());
132}
133
134int AbstractTreeView::count_visible_descendants(const AbstractTreeViewItem &parent) const
135{
136 if (parent.is_collapsed()) {
137 return 0;
138 }
139 int count = 0;
140 for (const auto &item : parent.children_) {
141 if (!item->is_filtered_visible()) {
142 continue;
143 }
144 count++;
145 count += count_visible_descendants(*item);
146 }
147
148 return count;
149}
150
151void AbstractTreeView::get_hierarchy_lines(const ARegion &region,
152 const TreeViewOrItem &parent,
153 const float aspect,
154 Vector<std::pair<int2, int2>> &lines,
155 int &visible_item_index) const
156{
157 const int scroll_ofs = scroll_value_ ? *scroll_value_ : 0;
158 const int max_visible_row_count = tot_visible_row_count().value_or(
159 std::numeric_limits<int>::max());
160
161 for (const auto &item : parent.children_) {
162 if (!item->is_filtered_visible()) {
163 continue;
164 }
165
166 const int item_index = visible_item_index;
167 visible_item_index++;
168
169 if (!item->is_collapsible() || item->is_collapsed()) {
170 continue;
171 }
172
173 /* Draw a hierarchy line for the descendants of this item. */
174
175 const AbstractTreeViewItem *first_descendant = item->children_.first().get();
176 const int descendant_count = count_visible_descendants(*item);
177
178 const int first_descendant_index = item_index + 1;
179 const int last_descendant_index = first_descendant_index + descendant_count;
180
181 {
182 const bool line_ends_above_visible = last_descendant_index < scroll_ofs;
183 if (line_ends_above_visible) {
184 continue;
185 }
186
187 const bool line_starts_below_visible = first_descendant_index >
188 (scroll_ofs + long(max_visible_row_count));
189 /* Can return here even, following items won't be in view anymore. */
190 if (line_starts_below_visible) {
191 return;
192 }
193 }
194
195 const int x = ((first_descendant->indent_width() + uiLayoutListItemPaddingWidth() -
196 (0.5f * UI_ICON_SIZE) + U.pixelsize + UI_SCALE_FAC) /
197 aspect);
198 const int ymax = std::max(0, first_descendant_index - scroll_ofs) * padded_item_height();
199 const int ymin = std::min(max_visible_row_count, last_descendant_index - scroll_ofs) *
201 lines.append(std::make_pair(int2(x, ymax), int2(x, ymin)));
202
203 this->get_hierarchy_lines(region, *item, aspect, lines, visible_item_index);
204 }
205}
206
208{
209 LISTBASE_FOREACH (uiBut *, but, &block.buttons) {
210 if (but->type != UI_BTYPE_VIEW_ITEM) {
211 continue;
212 }
213 uiButViewItem *view_item_but = static_cast<uiButViewItem *>(but);
214 if (&view_item_but->view_item->get_view() == &view) {
215 return view_item_but;
216 }
217 }
218 return nullptr;
219}
220
221void AbstractTreeView::draw_hierarchy_lines(const ARegion &region, const uiBlock &block) const
222{
223 const float aspect = (region.v2d.flag & V2D_IS_INIT) ?
224 BLI_rctf_size_y(&region.v2d.cur) /
225 (BLI_rcti_size_y(&region.v2d.mask) + 1) :
226 1.0f;
227
228 uiButViewItem *first_item_but = find_first_view_item_but(block, *this);
229 if (!first_item_but) {
230 return;
231 }
232
233 Vector<std::pair<int2, int2>> lines;
234 int index = 0;
235 get_hierarchy_lines(region, *this, aspect, lines, index);
236 if (lines.is_empty()) {
237 return;
238 }
239
244
245 GPU_line_width(1.0f / aspect);
247
248 rcti first_item_but_pixel_rect;
249 ui_but_to_pixelrect(&first_item_but_pixel_rect, &region, &block, first_item_but);
250 int2 top_left{first_item_but_pixel_rect.xmin, first_item_but_pixel_rect.ymax};
251
252 for (const auto &line : lines) {
254 immVertex2f(pos, top_left.x + line.first.x, top_left.y - line.first.y);
255 immVertex2f(pos, top_left.x + line.second.x, top_left.y - line.second.y);
256 immEnd();
257 }
259
261}
262
263void AbstractTreeView::draw_overlays(const ARegion &region, const uiBlock &block) const
264{
265 this->draw_hierarchy_lines(region, block);
266}
267
268void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
269{
270 const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
271
272 custom_height_ = old_tree_view.custom_height_;
273 scroll_value_ = old_tree_view.scroll_value_;
274 this->update_children_from_old_recursive(*this, old_tree_view);
275}
276
277void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
278 const TreeViewOrItem &old_items)
279{
280 /* This map can't find the exact old item for a new item. However, it can drastically reduce the
281 * number of items that need to be checked. */
283 for (const auto &old_item : old_items.children_) {
284 old_children_by_label.add(old_item->label_, old_item.get());
285 }
286
287 for (const auto &new_item : new_items.children_) {
288 const Span<AbstractTreeViewItem *> possible_old_children = old_children_by_label.lookup(
289 new_item->label_);
290 AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item,
291 possible_old_children);
292 if (!matching_old_item) {
293 continue;
294 }
295
296 new_item->update_from_old(*matching_old_item);
297
298 /* Recurse into children of the matched item. */
299 update_children_from_old_recursive(*new_item, *matching_old_item);
300 }
301}
302
303AbstractTreeViewItem *AbstractTreeView::find_matching_child(
304 const AbstractTreeViewItem &lookup_item, const Span<AbstractTreeViewItem *> possible_items)
305{
306 for (auto *iter_item : possible_items) {
307 if (lookup_item.matches(*iter_item)) {
308 /* We have a matching item! */
309 return iter_item;
310 }
311 }
312
313 return nullptr;
314}
315
316std::optional<int> AbstractTreeView::tot_visible_row_count() const
317{
318 if (!custom_height_) {
319 return {};
320 }
321 if (*custom_height_ < UI_UNIT_Y) {
322 return 1;
323 }
324 return round_fl_to_int(float(*custom_height_) / padded_item_height());
325}
326
327bool AbstractTreeView::supports_scrolling() const
328{
329 return custom_height_ && scroll_value_;
330}
331
333{
334 if (!supports_scrolling()) {
335 return;
336 }
337 /* Scroll value will be sanitized/clamped when drawing. */
338 *scroll_value_ += ((direction == ViewScrollDirection::UP) ? -1 : 1);
339}
340
341/* ---------------------------------------------------------------------- */
342
344 DropBehavior behavior)
345 : view_item_(view_item), behavior_(behavior)
346{
347}
348
350 const ARegion &region, const wmEvent &event) const
351{
353 return DropLocation::Into;
354 }
355
356 std::optional<rctf> win_rect = view_item_.get_win_rect(region);
357 if (!win_rect) {
359 return std::nullopt;
360 }
361 const float item_height = BLI_rctf_size_y(&*win_rect);
362
364
365 const int segment_count =
367 /* Divide into upper (insert before) and lower (insert after) half. */
368 2 :
369 /* Upper (insert before), middle (insert into) and lower (insert after) third. */
370 3;
371 const float segment_height = item_height / segment_count;
372
373 if (event.xy[1] - win_rect->ymin > (item_height - segment_height)) {
375 }
376 if (event.xy[1] - win_rect->ymin <= segment_height) {
379 {
380 /* Special case: Dropping at the lower 3rd of an uncollapsed item should insert into it, not
381 * after. */
382 return DropLocation::Into;
383 }
384 return DropLocation::After;
385 }
386
388 return DropLocation::Into;
389}
390
391/* ---------------------------------------------------------------------- */
392
393void AbstractTreeViewItem::tree_row_click_fn(bContext *C, void *but_arg1, void * /*arg2*/)
394{
395 uiButViewItem *item_but = (uiButViewItem *)but_arg1;
396 AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>(*item_but->view_item);
397
398 tree_item.activate(*C);
399}
400
401void AbstractTreeViewItem::add_treerow_button(uiBlock &block)
402{
403 /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */
404 view_item_but_ = reinterpret_cast<uiButViewItem *>(uiDefBut(&block,
406 0,
407 "",
408 0,
409 0,
410 UI_UNIT_X * 10,
412 nullptr,
413 0,
414 0,
415 ""));
416
419 UI_but_func_set(view_item_but_, tree_row_click_fn, view_item_but_, nullptr);
420}
421
422int AbstractTreeViewItem::indent_width() const
423{
424 return this->count_parents() * UI_TREEVIEW_INDENT;
425}
426
427void AbstractTreeViewItem::add_indent(uiLayout &row) const
428{
429 uiBlock *block = uiLayoutGetBlock(&row);
430 uiLayout *subrow = uiLayoutRow(&row, true);
431 uiLayoutSetFixedSize(subrow, true);
432
433 uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, this->indent_width(), 0, nullptr, 0.0, 0.0, "");
434
435 /* Indent items without collapsing icon some more within their parent. Makes it clear that they
436 * are actually nested and not just a row at the same level without a chevron. */
437 if (!this->is_collapsible()) {
438 uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, UI_TREEVIEW_INDENT, 0, nullptr, 0.0, 0.0, "");
439 }
440
441 /* Restore. */
442 UI_block_layout_set_current(block, &row);
443}
444
445void AbstractTreeViewItem::collapse_chevron_click_fn(bContext *C,
446 void * /*but_arg1*/,
447 void * /*arg2*/)
448{
449 /* There's no data we could pass to this callback. It must be either the button itself or a
450 * consistent address to match buttons over redraws. So instead of passing it somehow, just
451 * lookup the hovered item via context here. */
452
453 const wmWindow *win = CTX_wm_window(C);
455 AbstractViewItem *hovered_abstract_item = UI_region_views_find_item_at(*region,
456 win->eventstate->xy);
457
458 auto *hovered_item = reinterpret_cast<AbstractTreeViewItem *>(hovered_abstract_item);
459 BLI_assert(hovered_item != nullptr);
460
461 hovered_item->toggle_collapsed_from_view(*C);
462 /* When collapsing an item with an active child, make this collapsed item active instead so the
463 * active item stays visible. */
464 if (hovered_item->has_active_child()) {
465 hovered_item->activate(*C);
466 }
467}
468
469void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const
470{
471 if (!this->is_collapsible()) {
472 return;
473 }
474
475 const BIFIconID icon = this->is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT;
476 uiBut *but = uiDefIconBut(&block,
478 0,
479 icon,
480 0,
481 0,
483 UI_UNIT_Y,
484 nullptr,
485 0,
486 0,
487 "");
488 UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr);
490}
491
492void AbstractTreeViewItem::add_rename_button(uiLayout &row)
493{
494 uiBlock *block = uiLayoutGetBlock(&row);
495 eUIEmbossType previous_emboss = UI_block_emboss_get(block);
496
497 uiLayoutRow(&row, false);
498 /* Enable emboss for the text button. */
500
502
503 UI_block_emboss_set(block, previous_emboss);
504 UI_block_layout_set_current(block, &row);
505}
506
507bool AbstractTreeViewItem::has_active_child() const
508{
509 bool found = false;
510 foreach_item_recursive([&found](const AbstractTreeViewItem &item) {
511 if (item.is_active()) {
512 found = true;
513 }
514 });
515
516 return found;
517}
518
520{
521 return true;
522}
523
528
530{
531 /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single()
532 * recognizes the item. (It only compares labels by default.) */
533 label_ = new_name;
534 return true;
535}
536
538{
540
541 const AbstractTreeViewItem &old_tree_item = dynamic_cast<const AbstractTreeViewItem &>(old);
542 is_open_ = old_tree_item.is_open_;
543}
544
546{
547 return label_ == other.label_;
548}
549
550std::unique_ptr<DropTargetInterface> AbstractTreeViewItem::create_item_drop_target()
551{
552 return this->create_drop_target();
553}
554
555std::unique_ptr<TreeViewItemDropTarget> AbstractTreeViewItem::create_drop_target()
556{
557 return nullptr;
558}
559
560std::optional<std::string> AbstractTreeViewItem::debug_name() const
561{
562 return label_;
563}
564
566{
567 return dynamic_cast<AbstractTreeView &>(get_view());
568}
569
570std::optional<rctf> AbstractTreeViewItem::get_win_rect(const ARegion &region) const
571{
572 uiButViewItem *item_but = view_item_button();
573 if (!item_but) {
574 return std::nullopt;
575 }
576
577 rctf win_rect;
578 ui_block_to_window_rctf(&region, item_but->block, &win_rect, &item_but->rect);
579
580 return win_rect;
581}
582
584{
585 int i = 0;
586 for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
587 i++;
588 }
589 return i;
590}
591
592bool AbstractTreeViewItem::set_state_active()
593{
595 /* Make sure the active item is always visible. */
597 return true;
598 }
599
600 return false;
601}
602
604{
605 BLI_assert_msg(get_tree_view().is_reconstructed(),
606 "State can't be queried until reconstruction is completed");
608 "Hovered state can't be queried before the tree row is being built");
609
610 /* The new layout hasn't finished construction yet, so the final state of the button is unknown.
611 * Get the matching button from the previous redraw instead. */
613 *view_item_but_->block, *this);
614 return old_item_but && (old_item_but->flag & UI_HOVER);
615}
616
618{
619 BLI_assert_msg(get_tree_view().is_reconstructed(),
620 "State can't be queried until reconstruction is completed");
621 return this->is_collapsible() && !is_open_;
622}
623
625{
626 return this->set_collapsed(is_open_);
627}
628
629bool AbstractTreeViewItem::set_collapsed(const bool collapsed)
630{
631 if (!this->is_collapsible()) {
632 return false;
633 }
634 if (collapsed == !is_open_) {
635 return false;
636 }
637
638 is_open_ = !collapsed;
639 return true;
640}
641
643{
644 BLI_assert_msg(this->get_tree_view().is_reconstructed() == false,
645 "Default state should only be set while building the tree");
647 /* Set the open state. Note that this may be overridden later by #should_be_collapsed(). */
648 is_open_ = true;
649}
650
652{
653 BLI_assert_msg(get_tree_view().is_reconstructed(),
654 "State can't be queried until reconstruction is completed");
655 if (children_.is_empty()) {
656 return false;
657 }
658 return this->supports_collapsing();
659}
660
661void AbstractTreeViewItem::on_collapse_change(bContext & /*C*/, const bool /*is_collapsed*/)
662{
663 /* Do nothing by default. */
664}
665
667{
668 return std::nullopt;
669}
670
672{
673 if (this->toggle_collapsed()) {
674 this->on_collapse_change(C, this->is_collapsed());
675 }
676}
677
679{
681
682 const std::optional<bool> should_be_collapsed = this->should_be_collapsed();
683 if (should_be_collapsed.has_value()) {
684 /* This reflects an external state change and therefore shouldn't call #on_collapse_change().
685 */
686 this->set_collapsed(*should_be_collapsed);
687 }
688}
689
691{
692 for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
693 parent->set_collapsed(false);
694 }
695}
696
698{
699 const AbstractTreeViewItem &other_tree_item = dynamic_cast<const AbstractTreeViewItem &>(other);
700
701 if (!this->matches_single(other_tree_item)) {
702 return false;
703 }
704 if (this->count_parents() != other_tree_item.count_parents()) {
705 return false;
706 }
707
708 for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_;
709 parent && other_parent;
710 parent = parent->parent_, other_parent = other_parent->parent_)
711 {
712 if (!parent->matches_single(*other_parent)) {
713 return false;
714 }
715 }
716
717 return true;
718}
719
720/* ---------------------------------------------------------------------- */
721
723 uiBlock &block_;
724 bool add_box_ = true;
725
726 friend TreeViewBuilder;
727
728 public:
729 void build_from_tree(AbstractTreeView &tree_view);
730 void build_row(AbstractTreeViewItem &item) const;
731
732 uiBlock &block() const;
733 uiLayout &current_layout() const;
734
735 private:
736 /* Created through #TreeViewBuilder (friend class). */
738};
739
740TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiLayout &layout) : block_(*uiLayoutGetBlock(&layout))
741{
742}
743
745{
746 int item_count = 0;
747 tree_view.foreach_item([&](AbstractTreeViewItem &) { item_count++; },
750 return item_count;
751}
752
754{
755 uiLayout &parent_layout = this->current_layout();
756 uiBlock *block = uiLayoutGetBlock(&parent_layout);
757
758 uiLayout *col = nullptr;
759 if (add_box_) {
760 uiLayout *box = uiLayoutBox(&parent_layout);
761 col = uiLayoutColumn(box, true);
762 }
763 else {
764 col = uiLayoutColumn(&parent_layout, true);
765 }
766 /* Row for the tree-view and the scroll bar. */
767 uiLayout *row = uiLayoutRow(col, false);
768
769 const std::optional<int> visible_row_count = tree_view.tot_visible_row_count();
770 const int tot_items = count_visible_items(tree_view);
771
772 /* Column for the tree view. */
773 uiLayoutColumn(row, true);
774
775 /* Clamp scroll-value to valid range. */
776 if (tree_view.scroll_value_ && visible_row_count) {
777 *tree_view.scroll_value_ = std::clamp(
778 *tree_view.scroll_value_, 0, tot_items - *visible_row_count);
779 }
780
781 const int first_visible_index = tree_view.scroll_value_ ? *tree_view.scroll_value_ : 0;
782 const int max_visible_index = visible_row_count ? first_visible_index + *visible_row_count - 1 :
783 std::numeric_limits<int>::max();
784 int index = 0;
785 tree_view.foreach_item(
786 [&, this](AbstractTreeViewItem &item) {
787 if ((index >= first_visible_index) && (index <= max_visible_index)) {
788 this->build_row(item);
789 }
790 index++;
791 },
793
794 if (tree_view.custom_height_) {
795 uiLayoutColumn(row, false);
796
797 *tree_view.custom_height_ = visible_row_count.value_or(1) * padded_item_height();
798 if (!tree_view.scroll_value_) {
799 tree_view.scroll_value_ = std::make_unique<int>(0);
800 }
801
802 if (visible_row_count && (tot_items > *visible_row_count)) {
803 uiBut *but = uiDefButI(block,
805 0,
806 "",
807 0,
808 0,
810 *tree_view.custom_height_,
811 tree_view.scroll_value_.get(),
812 0,
813 tot_items - *visible_row_count,
814 "");
815 uiButScrollBar *but_scroll = reinterpret_cast<uiButScrollBar *>(but);
816 but_scroll->visual_height = *visible_row_count;
817 }
818
822 0,
823 ICON_GRIP,
824 0,
825 0,
826 UI_UNIT_X * 10,
827 UI_UNIT_Y * 0.5f,
828 tree_view.custom_height_.get(),
829 0,
830 0,
831 "");
832 }
833
834 UI_block_layout_set_current(block, &parent_layout);
835}
836
838{
839 uiBlock &block_ = block();
840
841 uiLayout &prev_layout = current_layout();
842 eUIEmbossType previous_emboss = UI_block_emboss_get(&block_);
843
844 uiLayout *overlap = uiLayoutOverlap(&prev_layout);
845
846 if (!item.is_interactive_) {
847 uiLayoutSetActive(overlap, false);
848 }
849
850 uiLayout *row = uiLayoutRow(overlap, false);
851 /* Enable emboss for mouse hover highlight. */
853 /* Every item gets one! Other buttons can be overlapped on top. */
854 item.add_treerow_button(block_);
855
856 /* After adding tree-row button (would disable hover highlighting). */
858
859 /* Add little margin to align actual contents vertically. */
860 uiLayout *content_col = uiLayoutColumn(overlap, true);
861 const int margin_top = (padded_item_height() - unpadded_item_height()) / 2;
862 if (margin_top > 0) {
863 uiDefBut(&block_, UI_BTYPE_LABEL, 0, "", 0, 0, UI_UNIT_X, margin_top, nullptr, 0, 0, "");
864 }
865 row = uiLayoutRow(content_col, true);
866
868 item.add_indent(*row);
869 item.add_collapse_chevron(block_);
870
871 if (item.is_renaming()) {
872 item.add_rename_button(*row);
873 }
874 else {
875 item.build_row(*row);
876 }
877
879
880 UI_block_emboss_set(&block_, previous_emboss);
881 UI_block_layout_set_current(&block_, &prev_layout);
882}
883
885{
886 return block_;
887}
888
893
894/* ---------------------------------------------------------------------- */
895
896void TreeViewBuilder::ensure_min_rows_items(AbstractTreeView &tree_view)
897{
898 const std::optional<int> visible_rows = tree_view.tot_visible_row_count();
899 if (!visible_rows) {
900 return;
901 }
902
903 int tot_visible_items = 0;
904 tree_view.foreach_item(
905 [&tot_visible_items](AbstractTreeViewItem & /*item*/) { tot_visible_items++; },
907
908 if (tot_visible_items >= *visible_rows) {
909 return;
910 }
911
912 for (int i = 0; i < (*visible_rows - tot_visible_items); i++) {
913 BasicTreeViewItem &new_item = tree_view.add_tree_item<BasicTreeViewItem>("");
914 new_item.disable_interaction();
915 }
916}
917
919 uiLayout &layout,
920 std::optional<StringRef> search_string,
921 const bool add_box)
922{
923 uiBlock &block = *uiLayoutGetBlock(&layout);
924
925 tree_view.build_tree();
926 tree_view.update_from_old(block);
927 tree_view.change_state_delayed();
928 tree_view.filter(search_string);
929
930 ensure_min_rows_items(tree_view);
931
932 /* Ensure the given layout is actually active. */
933 UI_block_layout_set_current(&block, &layout);
934
935 TreeViewLayoutBuilder builder(layout);
936 builder.add_box_ = add_box;
937 builder.build_from_tree(tree_view);
938}
939
940/* ---------------------------------------------------------------------- */
941
946
948{
949 this->add_label(row);
950}
951
953{
954 const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override;
955 uiItemL(&layout, IFACE_(label.c_str()), icon);
956}
957
958void BasicTreeViewItem::on_activate(bContext &C)
959{
960 if (activate_fn_) {
961 activate_fn_(C, *this);
962 }
963}
964
969
971{
972 is_active_fn_ = is_active_fn;
973}
974
975std::optional<bool> BasicTreeViewItem::should_be_active() const
976{
977 if (is_active_fn_) {
978 return is_active_fn_();
979 }
980 return std::nullopt;
981}
982
983} // 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:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define LISTBASE_FOREACH(type, var, list)
MINLINE int round_fl_to_int(float a)
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:193
bool BLI_rctf_isect_y(const rctf *rect, float y)
Definition rct.c:104
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:201
unsigned int uint
#define ELEM(...)
#define IFACE_(msgid)
#define UI_SCALE_FAC
#define UI_ICON_SIZE
@ V2D_IS_INIT
static AppView * view
void immUniformThemeColorAlpha(int color_id, float a)
void immEnd()
void immUnbindProgram()
void immVertex2f(uint attr_id, float x, float y)
void immBindBuiltinProgram(eGPUBuiltinShader shader_id)
GPUVertFormat * immVertexFormat()
void immBegin(GPUPrimType, uint vertex_len)
@ GPU_PRIM_LINES
@ GPU_SHADER_3D_UNIFORM_COLOR
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_line_width(float width)
Definition gpu_state.cc:161
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, const char *name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
void UI_but_func_set(uiBut *but, std::function< void(bContext &)> func)
void UI_but_flag_disable(uiBut *but, int flag)
#define UI_UNIT_Y
void uiLayoutSetActive(uiLayout *layout, bool active)
eUIEmbossType UI_block_emboss_get(uiBlock *block)
uiLayout * uiLayoutBox(uiLayout *layout)
eUIEmbossType
@ UI_EMBOSS
@ UI_EMBOSS_NONE_OR_STATUS
uiBut * uiDefButI(uiBlock *block, int type, int retval, blender::StringRef str, int x, int y, short width, short height, int *poin, float min, float max, const char *tip)
void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size)
uiBut * uiDefBut(uiBlock *block, int type, int retval, blender::StringRef str, int x, int y, short width, short height, void *poin, float min, float max, const char *tip)
void uiItemL(uiLayout *layout, const char *name, int icon)
uiBlock * uiLayoutGetBlock(uiLayout *layout)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
const uiStyle * UI_style_get_dpi()
uiBut * uiDefIconBut(uiBlock *block, int type, int retval, int icon, int x, int y, short width, short height, void *poin, float min, float max, const char *tip)
int uiLayoutListItemPaddingWidth()
void UI_block_emboss_set(uiBlock *block, eUIEmbossType emboss)
void uiLayoutSetEmboss(uiLayout *layout, eUIEmbossType emboss)
void uiLayoutListItemAddPadding(uiLayout *layout)
uiBut * uiDefIconButI(uiBlock *block, int type, int retval, int icon, int x, int y, short width, short height, int *poin, float min, float max, const char *tip)
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
uiLayout * uiLayoutOverlap(uiLayout *layout)
void UI_block_layout_set_current(uiBlock *block, uiLayout *layout)
#define UI_UNIT_X
@ UI_BTYPE_BUT_TOGGLE
@ UI_BTYPE_VIEW_ITEM
@ UI_BTYPE_LABEL
@ UI_BTYPE_SEPR
@ UI_BTYPE_GRIP
@ UI_BTYPE_SCROLL
blender::ui::AbstractViewItem * UI_region_views_find_item_at(const ARegion &region, const int xy[2])
@ UI_BUT_UNDO
@ TH_TEXT
int BIFIconID
#define V2D_SCROLL_WIDTH
Definition UI_view2d.hh:54
unsigned int U
Definition btGjkEpa3.h:78
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:570
void update_from_old(const AbstractViewItem &old) override
Definition tree_view.cc:537
AbstractTreeView & get_tree_view() const
Definition tree_view.cc:565
std::unique_ptr< DropTargetInterface > create_item_drop_target() final
Definition tree_view.cc:550
StringRef get_rename_string() const override
Definition tree_view.cc:524
virtual void build_row(uiLayout &row)=0
bool matches(const AbstractViewItem &other) const override
Definition tree_view.cc:697
void toggle_collapsed_from_view(bContext &C)
Definition tree_view.cc:671
virtual std::unique_ptr< TreeViewItemDropTarget > create_drop_target()
Definition tree_view.cc:555
virtual void on_collapse_change(bContext &C, bool is_collapsed)
Definition tree_view.cc:661
virtual std::optional< bool > should_be_collapsed() const
Definition tree_view.cc:666
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:629
virtual bool matches_single(const AbstractTreeViewItem &other) const
Definition tree_view.cc:545
std::optional< std::string > debug_name() const override
Definition tree_view.cc:560
virtual bool supports_collapsing() const
Definition tree_view.cc:519
bool rename(const bContext &C, StringRefNull new_name) override
Definition tree_view.cc:529
AbstractTreeViewItem * find_hovered(const ARegion &region, const int2 &xy)
Definition tree_view.cc:110
void scroll(ViewScrollDirection direction) override
Definition tree_view.cc:332
void draw_overlays(const ARegion &region, const uiBlock &block) const override
Definition tree_view.cc:263
void foreach_item(ItemIterFn iter_fn, IterOptions options=IterOptions::None) const
Definition tree_view.cc:105
void set_default_rows(int default_rows)
Definition tree_view.cc:129
virtual void update_from_old(const AbstractViewItem &old)
void add_rename_button(uiBlock &block)
uiButViewItem * view_item_button() 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)
Definition tree_view.cc:942
void build_row(uiLayout &row) override
Definition tree_view.cc:947
std::function< bool()> IsActiveFn
std::function< void(bContext &C, BasicTreeViewItem &new_active)> ActivateFn
void add_label(uiLayout &layout, StringRefNull label_override="")
Definition tree_view.cc:952
void set_is_active_fn(IsActiveFn is_active_fn)
Definition tree_view.cc:970
void set_on_activate_fn(ActivateFn fn)
Definition tree_view.cc:965
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)
AbstractTreeViewItem * parent_
TreeViewItemContainer * root_
void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options=IterOptions::None) const
Definition tree_view.cc:69
Vector< std::unique_ptr< AbstractTreeViewItem > > children_
void foreach_parent(ItemIterFn iter_fn) const
Definition tree_view.cc:89
AbstractTreeViewItem & view_item_
TreeViewItemDropTarget(AbstractTreeViewItem &view_item, DropBehavior behavior=DropBehavior::Insert)
Definition tree_view.cc:343
std::optional< DropLocation > choose_drop_location(const ARegion &region, const wmEvent &event) const override
Definition tree_view.cc:349
void build_from_tree(AbstractTreeView &tree_view)
Definition tree_view.cc:753
uiLayout & current_layout() const
Definition tree_view.cc:889
void build_row(AbstractTreeViewItem &item) const
Definition tree_view.cc:837
CCL_NAMESPACE_BEGIN struct Options options
const char * label
uint col
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:163
uiButViewItem * ui_block_view_find_matching_view_item_but_in_old_block(const uiBlock &new_block, const blender::ui::AbstractViewItem &new_item)
@ UI_HOVER
int count
format
static int unpadded_item_height()
Definition tree_view.cc:34
static int count_visible_items(AbstractTreeView &tree_view)
Definition tree_view.cc:744
static uiButViewItem * find_first_view_item_but(const uiBlock &block, const AbstractTreeView &view)
Definition tree_view.cc:207
static int padded_item_height()
Definition tree_view.cc:38
TreeViewItemContainer TreeViewOrItem
VecBase< int32_t, 2 > int2
int ymax
int xmin
uiLayout * curlayout
ListBase buttons
blender::ui::AbstractViewItem * view_item
uiBlock * block
int xy[2]
Definition WM_types.hh:726
struct wmEvent * eventstate
#define UI_TREEVIEW_INDENT
Definition tree_view.cc:32
int xy[2]
Definition wm_draw.cc:170