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