Blender V5.0
interface_template_node_tree_interface.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 "BKE_context.hh"
10#include "BKE_library.hh"
13
14#include "BLI_string.h"
15
16#include "BLT_translation.hh"
17
19
20#include "ED_node.hh"
21#include "ED_undo.hh"
22
23#include "RNA_access.hh"
24#include "RNA_prototypes.hh"
25
26#include "UI_interface.hh"
28#include "UI_resources.hh"
29#include "UI_tree_view.hh"
30
31#include "WM_api.hh"
32
34
35namespace blender::ui::nodes {
36
37namespace {
38
39using node_interface::bNodeTreeInterfaceItemReference;
40
41class NodePanelViewItem;
42class NodeSocketViewItem;
43class NodeTreeInterfaceView;
44
45class NodeTreeInterfaceDragController : public AbstractViewItemDragController {
46 private:
48 bNodeTree &tree_;
49
50 public:
51 explicit NodeTreeInterfaceDragController(NodeTreeInterfaceView &view,
54 ~NodeTreeInterfaceDragController() override = default;
55
56 std::optional<eWM_DragDataType> get_drag_type() const override;
57
58 void *create_drag_data() const override;
59};
60
61class NodeSocketDropTarget : public TreeViewItemDropTarget {
62 private:
64
65 public:
66 explicit NodeSocketDropTarget(NodeSocketViewItem &item, bNodeTreeInterfaceSocket &socket);
67
68 bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override;
69 std::string drop_tooltip(const DragInfo &drag_info) const override;
70 bool on_drop(bContext * /*C*/, const DragInfo &drag_info) const override;
71};
72
73class NodePanelDropTarget : public TreeViewItemDropTarget {
74 private:
76
77 public:
78 explicit NodePanelDropTarget(NodePanelViewItem &item, bNodeTreeInterfacePanel &panel);
79
80 bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override;
81 std::string drop_tooltip(const DragInfo &drag_info) const override;
82 bool on_drop(bContext *C, const DragInfo &drag_info) const override;
83};
84
85class NodeSocketViewItem : public BasicTreeViewItem {
86 private:
87 bNodeTree &nodetree_;
89
90 public:
91 NodeSocketViewItem(bNodeTree &nodetree,
92 bNodeTreeInterface &interface,
94 : BasicTreeViewItem(socket.name, ICON_NONE), nodetree_(nodetree), socket_(socket)
95 {
96 set_is_active_fn([interface, &socket]() { return interface.active_item() == &socket.item; });
97 set_on_activate_fn([&interface](bContext & /*C*/, BasicTreeViewItem &new_active) {
98 NodeSocketViewItem &self = static_cast<NodeSocketViewItem &>(new_active);
99 interface.active_item_set(&self.socket_.item);
100 });
101 }
102
103 void build_row(uiLayout &row) override
104 {
105 if (ID_IS_LINKED(&nodetree_)) {
106 row.enabled_set(false);
107 }
108
109 row.use_property_decorate_set(false);
110
111 uiLayout *input_socket_layout = &row.row(true);
112 if (socket_.flag & NODE_INTERFACE_SOCKET_INPUT) {
113 /* Context is not used by the template function. */
114 uiTemplateNodeSocket(input_socket_layout, /*C*/ nullptr, socket_.socket_color());
115 }
116 else {
117 /* Blank item to align output socket labels with inputs. */
118 input_socket_layout->label("", ICON_BLANK1);
119 }
120
121 this->add_label(row, IFACE_(label_.c_str()));
122
123 uiLayout *output_socket_layout = &row.row(true);
124 if (socket_.flag & NODE_INTERFACE_SOCKET_OUTPUT) {
125 /* Context is not used by the template function. */
126 uiTemplateNodeSocket(output_socket_layout, /*C*/ nullptr, socket_.socket_color());
127 }
128 else {
129 /* Blank item to align input socket labels with outputs. */
130 output_socket_layout->label("", ICON_BLANK1);
131 }
132 }
133
134 protected:
135 bool matches(const AbstractViewItem &other) const override
136 {
137 const NodeSocketViewItem *other_item = dynamic_cast<const NodeSocketViewItem *>(&other);
138 if (other_item == nullptr) {
139 return false;
140 }
141
142 return &socket_ == &other_item->socket_;
143 }
144
145 bool supports_renaming() const override
146 {
147 return !ID_IS_LINKED(&nodetree_);
148 }
149 bool rename(const bContext &C, StringRefNull new_name) override
150 {
151 MEM_SAFE_FREE(socket_.name);
152
153 socket_.name = BLI_strdup(new_name.c_str());
154 nodetree_.tree_interface.tag_item_property_changed();
156 ED_undo_push(&const_cast<bContext &>(C), new_name.c_str());
157 return true;
158 }
159 StringRef get_rename_string() const override
160 {
161 return socket_.name;
162 }
163
164 void delete_item(bContext *C) override
165 {
166 Main *bmain = CTX_data_main(C);
167 nodetree_.tree_interface.remove_item(socket_.item);
168 BKE_main_ensure_invariants(*bmain, nodetree_.id);
170 ED_undo_push(C, "Delete Node Interface Socket");
171 }
172
173 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override;
174 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override;
175};
176
177class NodePanelViewItem : public BasicTreeViewItem {
178 private:
179 bNodeTree &nodetree_;
181 const bNodeTreeInterfaceSocket *toggle_ = nullptr;
182
183 public:
184 NodePanelViewItem(bNodeTree &nodetree,
185 bNodeTreeInterface &interface,
187 : BasicTreeViewItem(panel.name, ICON_NONE), nodetree_(nodetree), panel_(panel)
188 {
189 set_is_active_fn([interface, &panel]() { return interface.active_item() == &panel.item; });
190 set_on_activate_fn([&interface](bContext & /*C*/, BasicTreeViewItem &new_active) {
191 NodePanelViewItem &self = static_cast<NodePanelViewItem &>(new_active);
192 interface.active_item_set(&self.panel_.item);
193 });
194 toggle_ = panel.header_toggle_socket();
195 is_always_collapsible_ = true;
196 }
197
198 void build_row(uiLayout &row) override
199 {
200 if (ID_IS_LINKED(&nodetree_)) {
201 row.enabled_set(false);
202 }
203 /* Add boolean socket if panel has a toggle. */
204 if (toggle_ != nullptr) {
205 uiLayout *toggle_layout = &row.row(true);
206 /* Context is not used by the template function. */
207 uiTemplateNodeSocket(toggle_layout, /*C*/ nullptr, toggle_->socket_color());
208 }
209
210 this->add_label(row, IFACE_(label_.c_str()));
211
212 uiLayout *sub = &row.row(true);
213 sub->use_property_decorate_set(false);
214 }
215
216 protected:
217 bool matches(const AbstractViewItem &other) const override
218 {
219 const NodePanelViewItem *other_item = dynamic_cast<const NodePanelViewItem *>(&other);
220 if (other_item == nullptr) {
221 return false;
222 }
223
224 return &panel_ == &other_item->panel_;
225 }
226
227 std::optional<bool> should_be_collapsed() const override
228 {
229 return panel_.flag & NODE_INTERFACE_PANEL_IS_COLLAPSED;
230 }
231
232 bool set_collapsed(const bool collapsed) override
233 {
234 if (!AbstractTreeViewItem::set_collapsed(collapsed)) {
235 return false;
236 }
238 return true;
239 }
240
241 bool supports_renaming() const override
242 {
243 return !ID_IS_LINKED(&nodetree_);
244 }
245 bool rename(const bContext &C, StringRefNull new_name) override
246 {
247 PointerRNA panel_ptr = RNA_pointer_create_discrete(
248 &nodetree_.id, &RNA_NodeTreeInterfacePanel, &panel_);
249 PropertyRNA *name_prop = RNA_struct_find_property(&panel_ptr, "name");
250 RNA_property_string_set(&panel_ptr, name_prop, new_name.c_str());
251 RNA_property_update(const_cast<bContext *>(&C), &panel_ptr, name_prop);
252 return true;
253 }
254 StringRef get_rename_string() const override
255 {
256 return panel_.name;
257 }
258
259 void delete_item(bContext *C) override
260 {
261 Main *bmain = CTX_data_main(C);
262 nodetree_.tree_interface.remove_item(panel_.item);
263 BKE_main_ensure_invariants(*bmain, nodetree_.id);
265 ED_undo_push(C, "Delete Node Interface Panel");
266 }
267
268 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override;
269 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override;
270};
271
272class NodeTreeInterfaceView : public AbstractTreeView {
273 private:
274 bNodeTree &nodetree_;
275 bNodeTreeInterface &interface_;
276
277 public:
278 explicit NodeTreeInterfaceView(bNodeTree &nodetree, bNodeTreeInterface &interface)
279 : nodetree_(nodetree), interface_(interface)
280 {
281 }
282
283 bNodeTree &nodetree()
284 {
285 return nodetree_;
286 }
287
289 {
290 return interface_;
291 }
292
293 void build_tree() override
294 {
295 /* Draw root items */
296 this->add_items_for_panel_recursive(interface_.root_panel, *this);
297 }
298
299 protected:
300 void add_items_for_panel_recursive(bNodeTreeInterfacePanel &parent,
301 ui::TreeViewOrItem &parent_item,
302 const bNodeTreeInterfaceItem *skip_item = nullptr)
303 {
304 for (bNodeTreeInterfaceItem *item : parent.items()) {
305 if (item == skip_item) {
306 continue;
307 }
308 switch (NodeTreeInterfaceItemType(item->item_type)) {
311 item);
312 NodeSocketViewItem &socket_item = parent_item.add_tree_item<NodeSocketViewItem>(
313 nodetree_, interface_, *socket);
314 socket_item.uncollapse_by_default();
315 break;
316 }
319 item);
320 NodePanelViewItem &panel_item = parent_item.add_tree_item<NodePanelViewItem>(
321 nodetree_, interface_, *panel);
322 panel_item.uncollapse_by_default();
323 /* Skip over sockets which are a panel toggle. */
324 const bNodeTreeInterfaceSocket *skip_item = panel->header_toggle_socket();
325 add_items_for_panel_recursive(
326 *panel, panel_item, reinterpret_cast<const bNodeTreeInterfaceItem *>(skip_item));
327 break;
328 }
329 }
330 }
331 }
332};
333
334std::unique_ptr<AbstractViewItemDragController> NodeSocketViewItem::create_drag_controller() const
335{
336 if (!ID_IS_EDITABLE(&nodetree_.id)) {
337 return nullptr;
338 }
339 return std::make_unique<NodeTreeInterfaceDragController>(
340 static_cast<NodeTreeInterfaceView &>(this->get_tree_view()), socket_.item, nodetree_);
341}
342
343std::unique_ptr<TreeViewItemDropTarget> NodeSocketViewItem::create_drop_target()
344{
345 return std::make_unique<NodeSocketDropTarget>(*this, socket_);
346}
347
348std::unique_ptr<AbstractViewItemDragController> NodePanelViewItem::create_drag_controller() const
349{
350 if (!ID_IS_EDITABLE(&nodetree_.id)) {
351 return nullptr;
352 }
353 return std::make_unique<NodeTreeInterfaceDragController>(
354 static_cast<NodeTreeInterfaceView &>(this->get_tree_view()), panel_.item, nodetree_);
355}
356
357std::unique_ptr<TreeViewItemDropTarget> NodePanelViewItem::create_drop_target()
358{
359 return std::make_unique<NodePanelDropTarget>(*this, panel_);
360}
361
362NodeTreeInterfaceDragController::NodeTreeInterfaceDragController(NodeTreeInterfaceView &view,
365 : AbstractViewItemDragController(view), item_(item), tree_(tree)
366{
367}
368
369std::optional<eWM_DragDataType> NodeTreeInterfaceDragController::get_drag_type() const
370{
372}
373
374void *NodeTreeInterfaceDragController::create_drag_data() const
375{
377 __func__);
378 drag_data->item = &item_;
379 drag_data->tree = &tree_;
380 return drag_data;
381}
382
383bNodeTreeInterfaceItemReference *get_drag_node_tree_declaration(const wmDrag &drag)
384{
386 return static_cast<bNodeTreeInterfaceItemReference *>(drag.poin);
387}
388
389bool is_dragging_parent_panel(const wmDrag &drag, const bNodeTreeInterfaceItem &drop_target_item)
390{
391 if (drag.type != WM_DRAG_NODE_TREE_INTERFACE) {
392 return false;
393 }
394 bNodeTreeInterfaceItemReference *drag_data = get_drag_node_tree_declaration(drag);
396 drag_data->item))
397 {
398 if (panel->contains(drop_target_item)) {
399 return true;
400 }
401 }
402 return false;
403}
404
405NodeSocketDropTarget::NodeSocketDropTarget(NodeSocketViewItem &item,
407 : TreeViewItemDropTarget(item, DropBehavior::Reorder), socket_(socket)
408{
409}
410
411bool NodeSocketDropTarget::can_drop(const wmDrag &drag, const char ** /*r_disabled_hint*/) const
412{
413 if (drag.type != WM_DRAG_NODE_TREE_INTERFACE) {
414 return false;
415 }
416 if (is_dragging_parent_panel(drag, socket_.item)) {
417 return false;
418 }
419 return true;
420}
421
422std::string NodeSocketDropTarget::drop_tooltip(const DragInfo &drag_info) const
423{
424 switch (drag_info.drop_location) {
425 case DropLocation::Into:
426 return "";
427 case DropLocation::Before:
428 return TIP_("Insert before socket");
429 case DropLocation::After:
430 return TIP_("Insert after socket");
431 }
432 return "";
433}
434
435bool on_drop_flat_item(bContext *C,
436 const DragInfo &drag_info,
437 bNodeTree &ntree,
438 bNodeTreeInterfaceItem &drop_target_item)
439{
440 bNodeTreeInterfaceItemReference *drag_data = get_drag_node_tree_declaration(drag_info.drag_data);
441 BLI_assert(drag_data != nullptr);
442 bNodeTreeInterfaceItem *drag_item = drag_data->item;
443 BLI_assert(drag_item != nullptr);
444
445 bNodeTreeInterface &interface = ntree.tree_interface;
446
447 bNodeTreeInterfacePanel *parent = interface.find_item_parent(drop_target_item, true);
448 int index = -1;
449
450 /* Insert into same panel as the target. */
451 BLI_assert(parent != nullptr);
452 switch (drag_info.drop_location) {
453 case DropLocation::Before:
454 index = parent->items().as_span().first_index_try(&drop_target_item);
455 break;
456 case DropLocation::After:
457 index = parent->items().as_span().first_index_try(&drop_target_item) + 1;
458 break;
459 default:
460 /* All valid cases should be handled above. */
462 break;
463 }
464 if (parent == nullptr || index < 0) {
465 return false;
466 }
467
468 interface.move_item_to_parent(*drag_item, parent, index);
469
470 /* General update */
472 ED_undo_push(C, "Insert node group item");
473 return true;
474}
475
476bool NodeSocketDropTarget::on_drop(bContext *C, const DragInfo &drag_info) const
477{
478 bNodeTree &nodetree = this->get_view<NodeTreeInterfaceView>().nodetree();
479 return on_drop_flat_item(C, drag_info, nodetree, socket_.item);
480}
481
482NodePanelDropTarget::NodePanelDropTarget(NodePanelViewItem &item, bNodeTreeInterfacePanel &panel)
483 : TreeViewItemDropTarget(item, DropBehavior::ReorderAndInsert), panel_(panel)
484{
485}
486
487bool NodePanelDropTarget::can_drop(const wmDrag &drag, const char ** /*r_disabled_hint*/) const
488{
489 if (drag.type != WM_DRAG_NODE_TREE_INTERFACE) {
490 return false;
491 }
492 bNodeTreeInterfaceItemReference *drag_data = get_drag_node_tree_declaration(drag);
493
494 /* Can't drop an item onto its children. */
496 drag_data->item))
497 {
498 if (panel->contains(panel_.item)) {
499 return false;
500 }
501 }
502
503 return true;
504}
505
506std::string NodePanelDropTarget::drop_tooltip(const DragInfo &drag_info) const
507{
508 switch (drag_info.drop_location) {
509 case DropLocation::Into:
510 return TIP_("Insert into panel");
511 case DropLocation::Before:
512 return TIP_("Insert before panel");
513 case DropLocation::After:
514 return TIP_("Insert after panel");
515 }
516 return "";
517}
518
519bool NodePanelDropTarget::on_drop(bContext *C, const DragInfo &drag_info) const
520{
521 bNodeTreeInterfaceItemReference *drag_data = get_drag_node_tree_declaration(drag_info.drag_data);
522 BLI_assert(drag_data != nullptr);
523 bNodeTreeInterfaceItem *drag_item = drag_data->item;
524 BLI_assert(drag_item != nullptr);
525
526 bNodeTree &nodetree = get_view<NodeTreeInterfaceView>().nodetree();
527 bNodeTreeInterface &interface = get_view<NodeTreeInterfaceView>().interface();
528
529 bNodeTreeInterfacePanel *parent = nullptr;
530 int index = -1;
531 switch (drag_info.drop_location) {
532 case DropLocation::Into: {
533 /* Insert into target */
534 parent = &panel_;
535 const bool has_toggle_socket = panel_.header_toggle_socket() != nullptr;
536 index = has_toggle_socket ? 1 : 0;
537 break;
538 }
539 case DropLocation::Before: {
540 /* Insert into same panel as the target. */
541 parent = interface.find_item_parent(panel_.item, true);
542 BLI_assert(parent != nullptr);
543 index = parent->items().as_span().first_index_try(&panel_.item);
544 break;
545 }
546 case DropLocation::After: {
547 /* Insert into same panel as the target. */
548 parent = interface.find_item_parent(panel_.item, true);
549 BLI_assert(parent != nullptr);
550 index = parent->items().as_span().first_index_try(&panel_.item) + 1;
551 break;
552 }
553 }
554 if (parent == nullptr || index < 0) {
555 return false;
556 }
557
558 interface.move_item_to_parent(*drag_item, parent, index);
559
560 /* General update */
562 ED_undo_push(C, "Insert node group item");
563 return true;
564}
565
566} // namespace
567
568} // namespace blender::ui::nodes
569
571{
572 if (!ptr->data) {
573 return;
574 }
575 if (!RNA_struct_is_a(ptr->type, &RNA_NodeTreeInterface)) {
576 return;
577 }
578 bNodeTree &nodetree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
579 bNodeTreeInterface &interface = *static_cast<bNodeTreeInterface *>(ptr->data);
580
581 uiBlock *block = layout->block();
582
584 *block,
585 "Node Tree Declaration Tree View",
586 std::make_unique<blender::ui::nodes::NodeTreeInterfaceView>(nodetree, interface));
587 tree_view->set_context_menu_title("Node Tree Interface");
588 tree_view->set_default_rows(5);
589
591}
Main * CTX_data_main(const bContext *C)
void BKE_main_ensure_invariants(Main &bmain, std::optional< blender::Span< ID * > > modified_ids=std::nullopt)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
#define SET_FLAG_FROM_TEST(value, test, flag)
#define TIP_(msgid)
#define IFACE_(msgid)
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:694
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:705
@ NODE_INTERFACE_PANEL_IS_COLLAPSED
struct bNodeTreeInterface bNodeTreeInterface
struct bNodeTreeInterfaceSocket bNodeTreeInterfaceSocket
struct bNodeTreeInterfacePanel bNodeTreeInterfacePanel
struct bNodeTreeInterfaceItem bNodeTreeInterfaceItem
struct bNodeTree bNodeTree
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
static AppView * view
#define MEM_SAFE_FREE(v)
#define C
Definition RandGen.cpp:29
blender::ui::AbstractGridView * UI_block_add_view(uiBlock &block, blender::StringRef idname, std::unique_ptr< blender::ui::AbstractGridView > grid_view)
void uiTemplateNodeSocket(uiLayout *layout, bContext *C, const float color[4])
#define NC_NODE
Definition WM_types.hh:394
@ WM_DRAG_NODE_TREE_INTERFACE
Definition WM_types.hh:1223
#define NA_EDITED
Definition WM_types.hh:584
PyObject * self
constexpr const char * c_str() const
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:715
void set_default_rows(int default_rows)
Definition tree_view.cc:141
void set_context_menu_title(const std::string &title)
static void build_tree_view(const bContext &C, AbstractTreeView &tree_view, uiLayout &layout, bool add_box=true)
KDTree_3d * tree
#define interface
void uiTemplateNodeTreeInterface(uiLayout *layout, const bContext *C, PointerRNA *ptr)
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
T & get_item_as(bNodeTreeInterfaceItem &item)
TreeViewItemContainer TreeViewOrItem
const char * name
bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
void use_property_decorate_set(bool is_sep)
uiBlock * block() const
void label(blender::StringRef name, int icon)
void enabled_set(bool enabled)
uiLayout & row(bool align)
eWM_DragDataType type
Definition WM_types.hh:1331
void * poin
Definition WM_types.hh:1332
void WM_main_add_notifier(uint type, void *reference)
PointerRNA * ptr
Definition wm_files.cc:4238