1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
|
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup editorui
*
* API for simple creation of tree UIs supporting advanced features.
* https://wiki.blender.org/wiki/Source/Interface/Views
*/
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <string>
#include "DNA_defs.h"
#include "BLI_function_ref.hh"
#include "BLI_vector.hh"
#include "UI_resources.h"
struct bContext;
struct PointerRNA;
struct uiBlock;
struct uiBut;
struct uiButTreeRow;
struct uiLayout;
struct wmEvent;
struct wmDrag;
namespace blender::ui {
class AbstractTreeView;
class AbstractTreeViewItem;
class AbstractTreeViewItemDropController;
/* ---------------------------------------------------------------------- */
/** \name Tree-View Item Container
* \{ */
/**
* Helper base class to expose common child-item data and functionality to both #AbstractTreeView
* and #AbstractTreeViewItem.
*
* That means this type can be used whenever either a #AbstractTreeView or a
* #AbstractTreeViewItem is needed.
*/
class TreeViewItemContainer {
friend class AbstractTreeView;
friend class AbstractTreeViewItem;
/* Private constructor, so only the friends above can create this! */
TreeViewItemContainer() = default;
protected:
Vector<std::unique_ptr<AbstractTreeViewItem>> children_;
/** Adding the first item to the root will set this, then it's passed on to all children. */
TreeViewItemContainer *root_ = nullptr;
/** Pointer back to the owning item. */
AbstractTreeViewItem *parent_ = nullptr;
public:
enum class IterOptions {
None = 0,
SkipCollapsed = 1 << 0,
/* Keep ENUM_OPERATORS() below updated! */
};
using ItemIterFn = FunctionRef<void(AbstractTreeViewItem &)>;
/**
* Convenience wrapper constructing the item by forwarding given arguments to the constructor of
* the type (\a ItemT).
*
* E.g. if your tree-item type has the following constructor:
* \code{.cpp}
* MyTreeItem(std::string str, int i);
* \endcode
* You can add an item like this:
* \code
* add_tree_item<MyTreeItem>("blabla", 42);
* \endcode
*/
template<class ItemT, typename... Args> inline ItemT &add_tree_item(Args &&...args);
/**
* Add an already constructed tree item to this parent. Ownership is moved to it.
* All tree items must be added through this, it handles important invariants!
*/
AbstractTreeViewItem &add_tree_item(std::unique_ptr<AbstractTreeViewItem> item);
protected:
void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
};
ENUM_OPERATORS(TreeViewItemContainer::IterOptions,
TreeViewItemContainer::IterOptions::SkipCollapsed);
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Tree-View Builders
* \{ */
class TreeViewBuilder {
uiBlock &block_;
public:
TreeViewBuilder(uiBlock &block);
void build_tree_view(AbstractTreeView &tree_view);
};
class TreeViewLayoutBuilder {
uiBlock &block_;
friend TreeViewBuilder;
public:
void build_row(AbstractTreeViewItem &item) const;
uiBlock &block() const;
uiLayout *current_layout() const;
private:
/* Created through #TreeViewBuilder. */
TreeViewLayoutBuilder(uiBlock &block);
static void polish_layout(const uiBlock &block);
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Tree-View Base Class
* \{ */
class AbstractTreeView : public TreeViewItemContainer {
friend AbstractTreeViewItem;
friend TreeViewBuilder;
/**
* Only one item can be renamed at a time. So the tree is informed about the renaming state to
* enforce that.
*/
std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_;
bool is_reconstructed_ = false;
public:
virtual ~AbstractTreeView() = default;
void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
/** Only one item can be renamed at a time. */
bool is_renaming() const;
/**
* Check if the tree is fully (re-)constructed. That means, both #build_tree() and
* #update_from_old() have finished.
*/
bool is_reconstructed() const;
protected:
virtual void build_tree() = 0;
private:
/**
* Match the tree-view against an earlier version of itself (if any) and copy the old UI state
* (e.g. collapsed, active, selected, renaming, etc.) to the new one. See
* #AbstractTreeViewItem.update_from_old().
*/
void update_from_old(uiBlock &new_block);
static void update_children_from_old_recursive(const TreeViewItemContainer &new_items,
const TreeViewItemContainer &old_items);
static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
const TreeViewItemContainer &items);
/**
* Items may want to do additional work when state changes. But these state changes can only be
* reliably detected after the tree has completed reconstruction (see #is_reconstructed()). So
* the actual state changes are done in a delayed manner through this function.
*/
void change_state_delayed();
void build_layout_from_tree(const TreeViewLayoutBuilder &builder);
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Tree-View Item Type
* \{ */
/** \brief Abstract base class for defining a customizable tree-view item.
*
* The tree-view item defines how to build its data into a tree-row. There are implementations for
* common layouts, e.g. #BasicTreeViewItem.
* It also stores state information that needs to be persistent over redraws, like the collapsed
* state.
*/
class AbstractTreeViewItem : public TreeViewItemContainer {
friend class AbstractTreeView;
friend class TreeViewLayoutBuilder;
public:
using IsActiveFn = std::function<bool()>;
private:
bool is_open_ = false;
bool is_active_ = false;
bool is_renaming_ = false;
IsActiveFn is_active_fn_;
protected:
/** This label is used for identifying an item (together with its parent's labels). */
std::string label_{};
/** Every item gets a button of type during the layout building #UI_BTYPE_TREEROW. */
uiButTreeRow *tree_row_but_ = nullptr;
public:
virtual ~AbstractTreeViewItem() = default;
virtual void build_row(uiLayout &row) = 0;
virtual void build_context_menu(bContext &C, uiLayout &column) const;
virtual void on_activate();
/**
* Set a custom callback to check if this item should be active. There's a version without
* arguments for checking if the item is currently in an active state.
*/
virtual void is_active(IsActiveFn is_active_fn);
/**
* Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if
* another item is already being renamed.
*/
virtual bool can_rename() const;
/**
* Try renaming the item, or the data it represents. Can assume
* #AbstractTreeViewItem::can_rename() returned true. Sub-classes that override this should
* usually call this, unless they have a custom #AbstractTreeViewItem.matches().
*
* \return True if the renaming was successful.
*/
virtual bool rename(StringRefNull new_name);
/**
* Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of
* the last redraw to this item. If sub-classes introduce more advanced state they should
* override this and make it update their state accordingly.
*/
virtual void update_from_old(const AbstractTreeViewItem &old);
/**
* Compare this item to \a other to check if they represent the same data.
* Used to recognize an item from a previous redraw, to be able to keep its state (e.g.
* open/closed, active, etc.). Items are only matched if their parents also match.
* By default this just matches the item's label (if the parents match!). If that isn't
* good enough for a sub-class, that can override it.
*/
virtual bool matches(const AbstractTreeViewItem &other) const;
/**
* If an item wants to support dropping data into it, it has to return a drop controller here.
* That is an object implementing #AbstractTreeViewItemDropController.
*
* \note This drop controller may be requested for each event. The tree-view doesn't keep a drop
* controller around currently. So it can not contain persistent state.
*/
virtual std::unique_ptr<AbstractTreeViewItemDropController> create_drop_controller() const;
void begin_renaming();
void end_renaming();
AbstractTreeView &get_tree_view() const;
int count_parents() const;
void deactivate();
/**
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_active() const;
/**
* Can be called from the #AbstractTreeViewItem::build_row() implementation, but not earlier. The
* hovered state can't be queried reliably otherwise.
* Note that this does a linear lookup in the old block, so isn't too great performance-wise.
*/
bool is_hovered() const;
void toggle_collapsed();
/**
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_collapsed() const;
void set_collapsed(bool collapsed);
bool is_collapsible() const;
bool is_renaming() const;
void ensure_parents_uncollapsed();
bool matches_including_parents(const AbstractTreeViewItem &other) const;
uiButTreeRow *tree_row_button();
protected:
/**
* Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate()
* function and ensures this item's parents are not collapsed (so the item is visible).
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise the
* actual item state is unknown, possibly calling state-change update functions incorrectly.
*/
void activate();
private:
static void rename_button_fn(bContext *, void *, char *);
static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but);
static void tree_row_click_fn(struct bContext *, void *, void *);
static void collapse_chevron_click_fn(bContext *, void *but_arg1, void *);
static bool is_collapse_chevron_but(const uiBut *but);
/** See #AbstractTreeView::change_state_delayed() */
void change_state_delayed();
void add_treerow_button(uiBlock &block);
void add_indent(uiLayout &row) const;
void add_collapse_chevron(uiBlock &block) const;
void add_rename_button(uiLayout &row);
bool has_active_child() const;
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Drag 'n Drop
* \{ */
/**
* Class to customize the drop behavior of a tree-item, plus the behavior when dragging over this
* item. An item can return a drop controller for itself via a custom implementation of
* #AbstractTreeViewItem::create_drop_controller().
*/
class AbstractTreeViewItemDropController {
protected:
AbstractTreeView &tree_view_;
public:
AbstractTreeViewItemDropController(AbstractTreeView &tree_view);
virtual ~AbstractTreeViewItemDropController() = default;
/**
* Check if the data dragged with \a drag can be dropped on the item this controller is for.
* \param r_disabled_hint: Return a static string to display to the user, explaining why dropping
* isn't possible on this item. Shouldn't be done too aggressively, e.g.
* don't set this if the drag-type can't be dropped here; only if it can
* but there's another reason it can't be dropped.
* Can assume this is a non-null pointer.
*/
virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0;
/**
* Custom text to display when dragging over a tree item. Should explain what happens when
* dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop()
* returns true, so the implementing override doesn't have to check that again.
* The returned value must be a translated string.
*/
virtual std::string drop_tooltip(const wmDrag &drag) const = 0;
/**
* Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this
* controller is for.
*/
virtual bool on_drop(const wmDrag &drag) = 0;
template<class TreeViewType> inline TreeViewType &tree_view() const;
};
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Predefined Tree-View Item Types
*
* Common, Basic Tree-View Item Types.
* \{ */
/**
* The most basic type, just a label with an icon.
*/
class BasicTreeViewItem : public AbstractTreeViewItem {
public:
using ActivateFn = std::function<void(BasicTreeViewItem &new_active)>;
BIFIconID icon;
explicit BasicTreeViewItem(StringRef label, BIFIconID icon = ICON_NONE);
void build_row(uiLayout &row) override;
void on_activate(ActivateFn fn);
protected:
/**
* Optionally passed to the #BasicTreeViewItem constructor. Called when activating this tree
* view item. This way users don't have to sub-class #BasicTreeViewItem, just to implement
* custom activation behavior (a common thing to do).
*/
ActivateFn activate_fn_;
private:
static void tree_row_click_fn(struct bContext *C, void *arg1, void *arg2);
void on_activate() override;
};
/** \} */
/* ---------------------------------------------------------------------- */
template<class ItemT, typename... Args>
inline ItemT &TreeViewItemContainer::add_tree_item(Args &&...args)
{
static_assert(std::is_base_of<AbstractTreeViewItem, ItemT>::value,
"Type must derive from and implement the AbstractTreeViewItem interface");
return dynamic_cast<ItemT &>(
add_tree_item(std::make_unique<ItemT>(std::forward<Args>(args)...)));
}
template<class TreeViewType> TreeViewType &AbstractTreeViewItemDropController::tree_view() const
{
return static_cast<TreeViewType &>(tree_view_);
}
} // namespace blender::ui
|