Move `Library.runtime` to be a pointer, move the related `LibraryRuntime` struct to `BKE_library.hh`. Similar to e.g. Mesh.runtime, that pointer is expected to always be valid, and is allocated at readtime or when creating a new Library ID. Related smaller changes: * Write code now uses standard ID writing codepath for Library IDs too. * Runtime pointer is reset to nullptr before writing. * Looking up a library by its absolute path is now handled through a dedicated utils, `search_filepath_abs`, instead of using `BLI_findstring`. Pull Request: https://projects.blender.org/blender/blender/pulls/134188
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*/) -> int {
|
|
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) -> int {
|
|
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*/) -> int {
|
|
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) -> int {
|
|
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
|