Callbacks: exec invoke & modal now use a typed enum wmOperatorStatus. This helps avoid mistakes returning incompatible booleans or other values which don't make sense for operators to return. It also makes it more obvious functions in the WM API are intended to be used to calculate return values for operator callbacks. Operator enums have been moved into DNA_windowmanager_enums.h so this can be used in other headers without loading other includes indirectly. No functional changes expected. Ref !136227
245 lines
8.2 KiB
C++
245 lines
8.2 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#pragma once
|
|
|
|
#include "NOD_socket_items.hh"
|
|
|
|
#include "WM_api.hh"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_library.hh"
|
|
#include "BKE_main_invariants.hh"
|
|
#include "BKE_node_tree_update.hh"
|
|
#include "BKE_node_tree_zones.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "ED_node.hh"
|
|
|
|
#include "DNA_space_types.h"
|
|
|
|
namespace blender::nodes::socket_items::ops {
|
|
|
|
inline PointerRNA get_active_node_to_operate_on(bContext *C, const int node_type)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
if (!snode) {
|
|
return PointerRNA_NULL;
|
|
}
|
|
if (!snode->edittree) {
|
|
return PointerRNA_NULL;
|
|
}
|
|
if (!ID_IS_EDITABLE(snode->edittree)) {
|
|
return PointerRNA_NULL;
|
|
}
|
|
const bke::bNodeTreeZones *zones = snode->edittree->zones();
|
|
if (!zones) {
|
|
return PointerRNA_NULL;
|
|
}
|
|
bNode *active_node = bke::node_get_active(*snode->edittree);
|
|
if (!active_node) {
|
|
return PointerRNA_NULL;
|
|
}
|
|
if (const bke::bNodeTreeZone *zone = zones->get_zone_by_node(active_node->identifier)) {
|
|
if (zone->input_node == active_node) {
|
|
/* Assume the data is generally stored on the output and not the input node. */
|
|
active_node = const_cast<bNode *>(zone->output_node);
|
|
}
|
|
}
|
|
if (active_node->type_legacy != node_type) {
|
|
return PointerRNA_NULL;
|
|
}
|
|
return RNA_pointer_create_discrete(&snode->edittree->id, &RNA_Node, active_node);
|
|
}
|
|
|
|
inline void update_after_node_change(bContext *C, const PointerRNA node_ptr)
|
|
{
|
|
bNode *node = static_cast<bNode *>(node_ptr.data);
|
|
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(node_ptr.owner_id);
|
|
|
|
BKE_ntree_update_tag_node_property(ntree, node);
|
|
BKE_main_ensure_invariants(*CTX_data_main(C), ntree->id);
|
|
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
|
|
}
|
|
|
|
template<typename Accessor> inline bool editable_node_active_poll(bContext *C)
|
|
{
|
|
return get_active_node_to_operate_on(C, Accessor::node_type).data != nullptr;
|
|
}
|
|
|
|
template<typename Accessor>
|
|
inline void remove_active_item(wmOperatorType *ot,
|
|
const char *name,
|
|
const char *idname,
|
|
const char *description)
|
|
{
|
|
ot->name = name;
|
|
ot->idname = idname;
|
|
ot->description = description;
|
|
ot->poll = editable_node_active_poll<Accessor>;
|
|
|
|
ot->exec = [](bContext *C, wmOperator * /*op*/) -> wmOperatorStatus {
|
|
PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type);
|
|
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
|
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
|
if (*ref.items_num > 0) {
|
|
dna::array::remove_index(
|
|
ref.items, ref.items_num, ref.active_index, *ref.active_index, Accessor::destruct_item);
|
|
update_after_node_change(C, node_ptr);
|
|
}
|
|
return OPERATOR_FINISHED;
|
|
};
|
|
}
|
|
|
|
template<typename Accessor>
|
|
inline void remove_item_by_index(wmOperatorType *ot,
|
|
const char *name,
|
|
const char *idname,
|
|
const char *description)
|
|
{
|
|
ot->name = name;
|
|
ot->idname = idname;
|
|
ot->description = description;
|
|
ot->poll = editable_node_active_poll<Accessor>;
|
|
|
|
ot->exec = [](bContext *C, wmOperator *op) -> wmOperatorStatus {
|
|
PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type);
|
|
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
|
const int index_to_remove = RNA_int_get(op->ptr, "index");
|
|
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
|
dna::array::remove_index(
|
|
ref.items, ref.items_num, ref.active_index, index_to_remove, Accessor::destruct_item);
|
|
|
|
update_after_node_change(C, node_ptr);
|
|
return OPERATOR_FINISHED;
|
|
};
|
|
|
|
RNA_def_int(ot->srna, "index", 0, 0, INT32_MAX, "Index", "Index to remove", 0, INT32_MAX);
|
|
}
|
|
|
|
template<typename Accessor>
|
|
inline void add_item(wmOperatorType *ot,
|
|
const char *name,
|
|
const char *idname,
|
|
const char *description)
|
|
{
|
|
ot->name = name;
|
|
ot->idname = idname;
|
|
ot->description = description;
|
|
ot->poll = editable_node_active_poll<Accessor>;
|
|
|
|
ot->exec = [](bContext *C, wmOperator * /*op*/) -> wmOperatorStatus {
|
|
PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type);
|
|
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
|
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
|
const typename Accessor::ItemT *active_item = nullptr;
|
|
int dst_index = *ref.items_num;
|
|
if (ref.active_index) {
|
|
const int old_active_index = *ref.active_index;
|
|
if (old_active_index >= 0 && old_active_index < *ref.items_num) {
|
|
active_item = &(*ref.items)[old_active_index];
|
|
dst_index = active_item ? old_active_index + 1 : *ref.items_num;
|
|
}
|
|
}
|
|
|
|
if constexpr (Accessor::has_type && Accessor::has_name) {
|
|
socket_items::add_item_with_socket_type_and_name<Accessor>(
|
|
node,
|
|
active_item ?
|
|
Accessor::get_socket_type(*active_item) :
|
|
(Accessor::supports_socket_type(SOCK_GEOMETRY) ? SOCK_GEOMETRY : SOCK_FLOAT),
|
|
/* Empty name so it is based on the type. */
|
|
active_item ? active_item->name : "");
|
|
}
|
|
else if constexpr (!Accessor::has_type && Accessor::has_name) {
|
|
socket_items::add_item_with_name<Accessor>(node, active_item ? active_item->name : "");
|
|
}
|
|
else if constexpr (!Accessor::has_type && !Accessor::has_name) {
|
|
socket_items::add_item<Accessor>(node);
|
|
}
|
|
else {
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
dna::array::move_index(*ref.items, *ref.items_num, *ref.items_num - 1, dst_index);
|
|
if (ref.active_index) {
|
|
*ref.active_index = dst_index;
|
|
}
|
|
|
|
update_after_node_change(C, node_ptr);
|
|
return OPERATOR_FINISHED;
|
|
};
|
|
}
|
|
|
|
enum class MoveDirection {
|
|
Up = 0,
|
|
Down = 1,
|
|
};
|
|
|
|
template<typename Accessor>
|
|
inline void move_active_item(wmOperatorType *ot,
|
|
const char *name,
|
|
const char *idname,
|
|
const char *description)
|
|
{
|
|
ot->name = name;
|
|
ot->idname = idname;
|
|
ot->description = description;
|
|
ot->poll = editable_node_active_poll<Accessor>;
|
|
|
|
ot->exec = [](bContext *C, wmOperator *op) -> wmOperatorStatus {
|
|
PointerRNA node_ptr = get_active_node_to_operate_on(C, Accessor::node_type);
|
|
bNode &node = *static_cast<bNode *>(node_ptr.data);
|
|
const MoveDirection direction = MoveDirection(RNA_enum_get(op->ptr, "direction"));
|
|
|
|
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
|
const int old_active_index = *ref.active_index;
|
|
if (direction == MoveDirection::Up && old_active_index > 0) {
|
|
dna::array::move_index(*ref.items, *ref.items_num, old_active_index, old_active_index - 1);
|
|
*ref.active_index -= 1;
|
|
}
|
|
else if (direction == MoveDirection::Down && old_active_index < *ref.items_num - 1) {
|
|
dna::array::move_index(*ref.items, *ref.items_num, old_active_index, old_active_index + 1);
|
|
*ref.active_index += 1;
|
|
}
|
|
|
|
update_after_node_change(C, node_ptr);
|
|
return OPERATOR_FINISHED;
|
|
};
|
|
|
|
static const EnumPropertyItem direction_items[] = {
|
|
{int(MoveDirection::Up), "UP", 0, "Up", ""},
|
|
{int(MoveDirection::Down), "DOWN", 0, "Down", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
RNA_def_enum(ot->srna, "direction", direction_items, 0, "Direction", "Move direction");
|
|
}
|
|
|
|
/**
|
|
* Creates simple operators for adding, removing and moving items.
|
|
* The idnames are passed in explicitly, so that they are more searchable compared to when they
|
|
* would be computed automatically.
|
|
*/
|
|
template<typename Accessor> inline void make_common_operators()
|
|
{
|
|
WM_operatortype_append([](wmOperatorType *ot) {
|
|
socket_items::ops::add_item<Accessor>(
|
|
ot, "Add Item", Accessor::operator_idnames::add_item, "Add item below active item");
|
|
});
|
|
WM_operatortype_append([](wmOperatorType *ot) {
|
|
socket_items::ops::remove_active_item<Accessor>(
|
|
ot, "Remove Item", Accessor::operator_idnames::remove_item, "Remove active item");
|
|
});
|
|
WM_operatortype_append([](wmOperatorType *ot) {
|
|
socket_items::ops::move_active_item<Accessor>(
|
|
ot, "Move Item", Accessor::operator_idnames::move_item, "Move active item");
|
|
});
|
|
}
|
|
|
|
} // namespace blender::nodes::socket_items::ops
|