Blender  V3.3
tree_view.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
7 #include "DNA_userdef_types.h"
9 
10 #include "BKE_context.h"
11 
12 #include "BLT_translation.h"
13 
14 #include "interface_intern.h"
15 
16 #include "UI_interface.h"
17 
18 #include "WM_api.h"
19 #include "WM_types.h"
20 
21 #include "UI_tree_view.hh"
22 
23 namespace blender::ui {
24 
25 /* ---------------------------------------------------------------------- */
26 
32  std::unique_ptr<AbstractTreeViewItem> item)
33 {
34  children_.append(std::move(item));
35 
36  /* The first item that will be added to the root sets this. */
37  if (root_ == nullptr) {
38  root_ = this;
39  }
40  AbstractTreeView &tree_view = static_cast<AbstractTreeView &>(*root_);
41  AbstractTreeViewItem &added_item = *children_.last();
42  added_item.root_ = root_;
43  tree_view.register_item(added_item);
44 
45  if (root_ != this) {
46  /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely
47  * nice to static_cast this, but well... */
48  added_item.parent_ = static_cast<AbstractTreeViewItem *>(this);
49  }
50 
51  return added_item;
52 }
53 
55 {
56  for (const auto &child : children_) {
57  iter_fn(*child);
58  if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) {
59  continue;
60  }
61 
62  child->foreach_item_recursive(iter_fn, options);
63  }
64 }
65 
66 /* ---------------------------------------------------------------------- */
67 
69 {
71 }
72 
73 void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
74 {
75  const AbstractTreeView &old_tree_view = dynamic_cast<const AbstractTreeView &>(old_view);
76 
77  update_children_from_old_recursive(*this, old_tree_view);
78 }
79 
80 void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
81  const TreeViewOrItem &old_items)
82 {
83  for (const auto &new_item : new_items.children_) {
84  AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items);
85  if (!matching_old_item) {
86  continue;
87  }
88 
89  new_item->update_from_old(*matching_old_item);
90 
91  /* Recurse into children of the matched item. */
92  update_children_from_old_recursive(*new_item, *matching_old_item);
93  }
94 }
95 
96 AbstractTreeViewItem *AbstractTreeView::find_matching_child(
97  const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items)
98 {
99  for (const auto &iter_item : items.children_) {
100  if (lookup_item.matches_single(*iter_item)) {
101  /* We have a matching item! */
102  return iter_item.get();
103  }
104  }
105 
106  return nullptr;
107 }
108 
109 void AbstractTreeView::change_state_delayed()
110 {
113  "These state changes are supposed to be delayed until reconstruction is completed");
114  foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); });
115 }
116 
117 /* ---------------------------------------------------------------------- */
118 
119 void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/,
120  void *but_arg1,
121  void * /*arg2*/)
122 {
123  uiButViewItem *item_but = (uiButViewItem *)but_arg1;
124  AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>(*item_but->view_item);
125 
126  tree_item.activate();
127  /* Not only activate the item, also show its children. Maybe this should be optional, or
128  * controlled by the specific tree-view. */
129  tree_item.set_collapsed(false);
130 }
131 
132 void AbstractTreeViewItem::add_treerow_button(uiBlock &block)
133 {
134  /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */
136  &block, UI_BTYPE_VIEW_ITEM, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, "");
137 
138  view_item_but_->view_item = reinterpret_cast<uiViewItemHandle *>(this);
139  UI_but_func_set(&view_item_but_->but, tree_row_click_fn, view_item_but_, nullptr);
140 }
141 
142 void AbstractTreeViewItem::add_indent(uiLayout &row) const
143 {
144  uiBlock *block = uiLayoutGetBlock(&row);
145  uiLayout *subrow = uiLayoutRow(&row, true);
146  uiLayoutSetFixedSize(subrow, true);
147 
148  const float indent_size = count_parents() * UI_DPI_ICON_SIZE;
149  uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, "");
150 
151  /* Indent items without collapsing icon some more within their parent. Makes it clear that they
152  * are actually nested and not just a row at the same level without a chevron. */
153  if (!is_collapsible() && parent_) {
154  uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, "");
155  }
156 
157  /* Restore. */
158  UI_block_layout_set_current(block, &row);
159 }
160 
161 void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C,
162  void * /*but_arg1*/,
163  void * /*arg2*/)
164 {
165  /* There's no data we could pass to this callback. It must be either the button itself or a
166  * consistent address to match buttons over redraws. So instead of passing it somehow, just
167  * lookup the hovered item via context here. */
168 
169  const wmWindow *win = CTX_wm_window(C);
170  const ARegion *region = CTX_wm_region(C);
171  uiViewItemHandle *hovered_item_handle = UI_region_views_find_item_at(region,
172  win->eventstate->xy);
173 
174  AbstractTreeViewItem *hovered_item = from_item_handle<AbstractTreeViewItem>(hovered_item_handle);
175  BLI_assert(hovered_item != nullptr);
176 
177  hovered_item->toggle_collapsed();
178  /* When collapsing an item with an active child, make this collapsed item active instead so the
179  * active item stays visible. */
180  if (hovered_item->has_active_child()) {
181  hovered_item->activate();
182  }
183 }
184 
185 bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but)
186 {
187  return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) &&
188  (but->func == collapse_chevron_click_fn);
189 }
190 
191 void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const
192 {
193  if (!is_collapsible()) {
194  return;
195  }
196 
197  const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN;
198  uiBut *but = uiDefIconBut(
199  &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, "");
200  /* Note that we're passing the tree-row button here, not the chevron one. */
201  UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr);
203 
204  /* Check if the query for the button matches the created button. */
205  BLI_assert(is_collapse_chevron_but(but));
206 }
207 
208 void AbstractTreeViewItem::add_rename_button(uiLayout &row)
209 {
210  uiBlock *block = uiLayoutGetBlock(&row);
211  eUIEmbossType previous_emboss = UI_block_emboss_get(block);
212 
213  uiLayoutRow(&row, false);
214  /* Enable emboss for the text button. */
216 
218 
219  UI_block_emboss_set(block, previous_emboss);
220  UI_block_layout_set_current(block, &row);
221 }
222 
223 bool AbstractTreeViewItem::has_active_child() const
224 {
225  bool found = false;
226  foreach_item_recursive([&found](const AbstractTreeViewItem &item) {
227  if (item.is_active()) {
228  found = true;
229  }
230  });
231 
232  return found;
233 }
234 
236 {
237  /* Do nothing by default. */
238 }
239 
240 std::optional<bool> AbstractTreeViewItem::should_be_active() const
241 {
242  return std::nullopt;
243 }
244 
246 {
247  return true;
248 }
249 
251 {
252  return label_;
253 }
254 
256 {
257  /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single()
258  * recognizes the item. (It only compares labels by default.) */
259  label_ = new_name;
260  return true;
261 }
262 
264 {
266 
267  const AbstractTreeViewItem &old_tree_item = dynamic_cast<const AbstractTreeViewItem &>(old);
268  is_open_ = old_tree_item.is_open_;
269 }
270 
272 {
273  return label_ == other.label_;
274 }
275 
277 {
278  return dynamic_cast<AbstractTreeView &>(get_view());
279 }
280 
281 int AbstractTreeViewItem::count_parents() const
282 {
283  int i = 0;
284  for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
285  i++;
286  }
287  return i;
288 }
289 
291 {
292  BLI_assert_msg(get_tree_view().is_reconstructed(),
293  "Item activation can't be done until reconstruction is completed");
294 
295  if (is_active()) {
296  return;
297  }
298 
299  /* Deactivate other items in the tree. */
300  get_tree_view().foreach_item([](auto &item) { item.deactivate(); });
301 
302  on_activate();
303  /* Make sure the active item is always visible. */
305 
306  is_active_ = true;
307 }
308 
310 {
311  is_active_ = false;
312 }
313 
315 {
316  BLI_assert_msg(get_tree_view().is_reconstructed(),
317  "State can't be queried until reconstruction is completed");
318  BLI_assert_msg(view_item_but_ != nullptr,
319  "Hovered state can't be queried before the tree row is being built");
320 
321  const uiViewItemHandle *this_item_handle = reinterpret_cast<const uiViewItemHandle *>(this);
322  /* The new layout hasn't finished construction yet, so the final state of the button is unknown.
323  * Get the matching button from the previous redraw instead. */
325  view_item_but_->but.block, this_item_handle);
326  return old_item_but && (old_item_but->but.flag & UI_ACTIVE);
327 }
328 
330 {
331  BLI_assert_msg(get_tree_view().is_reconstructed(),
332  "State can't be queried until reconstruction is completed");
333  return is_collapsible() && !is_open_;
334 }
335 
337 {
338  is_open_ = !is_open_;
339 }
340 
342 {
343  is_open_ = !collapsed;
344 }
345 
347 {
348  if (children_.is_empty()) {
349  return false;
350  }
351  return this->supports_collapsing();
352 }
353 
355 {
356  for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
357  parent->set_collapsed(false);
358  }
359 }
360 
362 {
363  const AbstractTreeViewItem &other_tree_item = dynamic_cast<const AbstractTreeViewItem &>(other);
364 
365  if (!matches_single(other_tree_item)) {
366  return false;
367  }
368  if (count_parents() != other_tree_item.count_parents()) {
369  return false;
370  }
371 
372  for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_;
373  parent && other_parent;
374  parent = parent->parent_, other_parent = other_parent->parent_) {
375  if (!parent->matches_single(*other_parent)) {
376  return false;
377  }
378  }
379 
380  return true;
381 }
382 
384 {
385  return view_item_but_;
386 }
387 
388 void AbstractTreeViewItem::change_state_delayed()
389 {
390  const std::optional<bool> should_be_active = this->should_be_active();
391  if (should_be_active.has_value() && *should_be_active) {
392  activate();
393  }
394 }
395 
396 /* ---------------------------------------------------------------------- */
397 
399  uiBlock &block_;
400 
401  friend TreeViewBuilder;
402 
403  public:
404  void build_from_tree(const AbstractTreeView &tree_view);
405  void build_row(AbstractTreeViewItem &item) const;
406 
407  uiBlock &block() const;
408  uiLayout *current_layout() const;
409 
410  private:
411  /* Created through #TreeViewBuilder. */
413 
414  static void polish_layout(const uiBlock &block);
415 };
416 
417 TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block)
418 {
419 }
420 
422 {
423  uiLayout *prev_layout = current_layout();
424 
425  uiLayout *box = uiLayoutBox(prev_layout);
426  uiLayoutColumn(box, false);
427 
428  tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); },
430 
431  UI_block_layout_set_current(&block(), prev_layout);
432 }
433 
434 void TreeViewLayoutBuilder::polish_layout(const uiBlock &block)
435 {
437  if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next &&
438  /* Embossed buttons with padding-less text padding look weird, so don't touch them. */
441  }
442 
443  if (but->type == UI_BTYPE_VIEW_ITEM) {
444  break;
445  }
446  }
447 }
448 
450 {
451  uiBlock &block_ = block();
452 
453  uiLayout *prev_layout = current_layout();
454  eUIEmbossType previous_emboss = UI_block_emboss_get(&block_);
455 
456  uiLayout *overlap = uiLayoutOverlap(prev_layout);
457 
458  uiLayoutRow(overlap, false);
459  /* Every item gets one! Other buttons can be overlapped on top. */
460  item.add_treerow_button(block_);
461 
462  /* After adding tree-row button (would disable hover highlighting). */
464 
465  uiLayout *row = uiLayoutRow(overlap, true);
466  item.add_indent(*row);
467  item.add_collapse_chevron(block_);
468 
469  if (item.is_renaming()) {
470  item.add_rename_button(*row);
471  }
472  else {
473  item.build_row(*row);
474  }
475  polish_layout(block_);
476 
477  UI_block_emboss_set(&block_, previous_emboss);
478  UI_block_layout_set_current(&block_, prev_layout);
479 }
480 
482 {
483  return block_;
484 }
485 
487 {
488  return block().curlayout;
489 }
490 
491 /* ---------------------------------------------------------------------- */
492 
494 {
495 }
496 
498 {
499  tree_view.build_tree();
500  tree_view.update_from_old(block_);
501  tree_view.change_state_delayed();
502 
503  TreeViewLayoutBuilder builder(block_);
504  builder.build_from_tree(tree_view);
505 }
506 
507 /* ---------------------------------------------------------------------- */
508 
510 {
511  label_ = label;
512 }
513 
515 {
516  add_label(row);
517 }
518 
520 {
521  const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override;
522 
523  /* Some padding for labels without collapse chevron and no icon. Looks weird without. */
524  if (icon == ICON_NONE && !is_collapsible()) {
525  uiItemS_ex(&layout, 0.8f);
526  }
527  uiItemL(&layout, IFACE_(label.c_str()), icon);
528 }
529 
530 void BasicTreeViewItem::on_activate()
531 {
532  if (activate_fn_) {
533  activate_fn_(*this);
534  }
535 }
536 
538 {
539  activate_fn_ = fn;
540 }
541 
543 {
544  is_active_fn_ = is_active_fn;
545 }
546 
547 std::optional<bool> BasicTreeViewItem::should_be_active() const
548 {
549  if (is_active_fn_) {
550  return is_active_fn_();
551  }
552  return std::nullopt;
553 }
554 
555 } // namespace blender::ui
struct ARegion * CTX_wm_region(const bContext *C)
Definition: context.c:749
struct wmWindow * CTX_wm_window(const bContext *C)
Definition: context.c:723
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition: BLI_assert.h:53
#define LISTBASE_FOREACH_BACKWARD(type, var, list)
Definition: BLI_listbase.h:348
#define ELEM(...)
#define IFACE_(msgid)
void uiItemS_ex(uiLayout *layout, float factor)
@ UI_BUT_NO_TEXT_PADDING
Definition: UI_interface.h:265
void UI_but_flag_disable(uiBut *but, int flag)
Definition: interface.cc:5863
#define UI_UNIT_Y
eUIEmbossType UI_block_emboss_get(uiBlock *block)
Definition: interface.cc:3624
uiBlock * uiLayoutGetBlock(uiLayout *layout)
@ UI_BUT_UNDO
Definition: UI_interface.h:205
eUIEmbossType
Definition: UI_interface.h:107
@ UI_EMBOSS_NONE
Definition: UI_interface.h:109
@ UI_EMBOSS
Definition: UI_interface.h:108
@ UI_EMBOSS_NONE_OR_STATUS
Definition: UI_interface.h:116
uiBut * uiDefIconBut(uiBlock *block, int type, int retval, int icon, int x, int y, short width, short height, void *poin, float min, float max, float a1, float a2, const char *tip)
Definition: interface.cc:5336
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
uiLayout * uiLayoutOverlap(uiLayout *layout)
void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size)
uiBut * uiDefBut(uiBlock *block, int type, int retval, const char *str, int x, int y, short width, short height, void *poin, float min, float max, float a1, float a2, const char *tip)
Definition: interface.cc:4806
void uiItemL(uiLayout *layout, const char *name, int icon)
uiViewItemHandle * UI_region_views_find_item_at(const struct ARegion *region, const int xy[2]) ATTR_NONNULL()
void UI_but_drawflag_enable(uiBut *but, int flag)
Definition: interface.cc:5873
uiLayout * uiLayoutBox(uiLayout *layout)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void UI_block_emboss_set(uiBlock *block, eUIEmbossType emboss)
Definition: interface.cc:3629
#define UI_DPI_ICON_SIZE
Definition: UI_interface.h:307
void UI_but_func_set(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2)
Definition: interface.cc:6000
void UI_block_layout_set_current(uiBlock *block, uiLayout *layout)
#define UI_UNIT_X
struct uiViewItemHandle uiViewItemHandle
Definition: UI_interface.h:78
@ UI_BTYPE_BUT_TOGGLE
Definition: UI_interface.h:345
@ UI_BTYPE_VIEW_ITEM
Definition: UI_interface.h:393
@ UI_BTYPE_SEPR
Definition: UI_interface.h:385
BIFIconID
Definition: UI_resources.h:18
constexpr bool is_empty() const
Abstract base class for defining a customizable tree-view item.
virtual void update_from_old(const AbstractViewItem &old) override
Definition: tree_view.cc:263
uiButViewItem * view_item_button()
Definition: tree_view.cc:383
AbstractTreeView & get_tree_view() const
Definition: tree_view.cc:276
virtual StringRef get_rename_string() const override
Definition: tree_view.cc:250
virtual void build_row(uiLayout &row)=0
virtual bool matches(const AbstractViewItem &other) const override
Definition: tree_view.cc:361
virtual bool matches_single(const AbstractTreeViewItem &other) const
Definition: tree_view.cc:271
virtual std::optional< bool > should_be_active() const
Definition: tree_view.cc:240
virtual bool supports_collapsing() const
Definition: tree_view.cc:245
virtual bool rename(StringRefNull new_name) override
Definition: tree_view.cc:255
void set_collapsed(bool collapsed)
Definition: tree_view.cc:341
void foreach_item(ItemIterFn iter_fn, IterOptions options=IterOptions::None) const
Definition: tree_view.cc:68
virtual void update_from_old(const AbstractViewItem &old)
void add_rename_button(uiBlock &block)
AbstractView & get_view() const
void update_from_old(uiBlock &new_block)
void register_item(AbstractViewItem &item)
void set_is_active_fn(IsActiveFn fn)
Definition: tree_view.cc:542
BasicTreeViewItem(StringRef label, BIFIconID icon=ICON_NONE)
Definition: tree_view.cc:509
void build_row(uiLayout &row) override
Definition: tree_view.cc:514
std::function< bool()> IsActiveFn
std::function< void(BasicTreeViewItem &new_active)> ActivateFn
void add_label(uiLayout &layout, StringRefNull label_override="")
Definition: tree_view.cc:519
void set_on_activate_fn(ActivateFn fn)
Definition: tree_view.cc:537
TreeViewBuilder(uiBlock &block)
Definition: tree_view.cc:493
void build_tree_view(AbstractTreeView &tree_view)
Definition: tree_view.cc:497
ItemT & add_tree_item(Args &&...args)
AbstractTreeViewItem * parent_
Definition: UI_tree_view.hh:61
TreeViewItemContainer * root_
Definition: UI_tree_view.hh:59
void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options=IterOptions::None) const
Definition: tree_view.cc:54
Vector< std::unique_ptr< AbstractTreeViewItem > > children_
Definition: UI_tree_view.hh:57
void build_from_tree(const AbstractTreeView &tree_view)
Definition: tree_view.cc:421
uiLayout * current_layout() const
Definition: tree_view.cc:486
void build_row(AbstractTreeViewItem &item) const
Definition: tree_view.cc:449
CCL_NAMESPACE_BEGIN struct Options options
const char * label
uiButViewItem * ui_block_view_find_matching_view_item_but_in_old_block(const uiBlock *new_block, const uiViewItemHandle *new_item_handle)
@ UI_ACTIVE
TreeViewItemContainer TreeViewOrItem
ListBase buttons
struct uiLayout * curlayout
uiViewItemHandle * view_item
struct uiBut * next
eButType type
uiButHandleFunc func
uiBlock * block
eUIEmbossType emboss
BIFIconID icon
int xy[2]
Definition: WM_types.h:682
struct wmEvent * eventstate