The order of collections in the light linking collection is important as it is used for tie-breaking when the same object is reachable from multiple linked collections with different light linking state. This commit utilizes the generic tree view reordering operations to allow insertion of elements to a specific location and to re-order existing objects and collections in the light linking collection. It is possible to re-order both collections and objects, even tough re-ordering objects does not really make a difference for the light linking behavior. It is not possible to place an object before collections. Pull Request: https://projects.blender.org/blender/blender/pulls/112849
393 lines
11 KiB
C++
393 lines
11 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edinterface
|
|
*/
|
|
|
|
#include "UI_interface.hh"
|
|
|
|
#include <cstdio>
|
|
#include <memory>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_light_linking.h"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_prototypes.h"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
#include "UI_tree_view.hh"
|
|
|
|
#include "WM_api.hh"
|
|
|
|
#include "ED_undo.hh"
|
|
|
|
namespace blender::ui::light_linking {
|
|
|
|
namespace {
|
|
|
|
class BaseCollectionDropTarget : public TreeViewItemDropTarget {
|
|
Collection &collection_;
|
|
|
|
public:
|
|
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
|
|
{
|
|
if (drag.type != WM_DRAG_ID) {
|
|
return false;
|
|
}
|
|
|
|
const wmDragID *drag_id = static_cast<wmDragID *>(drag.ids.first);
|
|
if (!drag_id) {
|
|
return false;
|
|
}
|
|
|
|
/* The dragged IDs are guaranteed to be the same type, so only check the type of the first one.
|
|
*/
|
|
const ID_Type id_type = GS(drag_id->id->name);
|
|
if (!ELEM(id_type, ID_OB, ID_GR)) {
|
|
*r_disabled_hint = "Can only add objects and collections to the light linking collection";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected:
|
|
BaseCollectionDropTarget(AbstractTreeView &view, DropBehavior behavior, Collection &collection)
|
|
: TreeViewItemDropTarget(view, behavior), collection_(collection)
|
|
{
|
|
}
|
|
|
|
Collection &get_collection() const
|
|
{
|
|
return collection_;
|
|
}
|
|
};
|
|
|
|
class InsertCollectionDropTarget : public BaseCollectionDropTarget {
|
|
public:
|
|
InsertCollectionDropTarget(AbstractTreeView &view, Collection &collection)
|
|
: BaseCollectionDropTarget(view, DropBehavior::Insert, collection)
|
|
{
|
|
}
|
|
|
|
std::string drop_tooltip(const DragInfo & /*drag*/) const override
|
|
{
|
|
return TIP_("Add to linking collection");
|
|
}
|
|
|
|
bool on_drop(bContext *C, const DragInfo &drag) const override
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
LISTBASE_FOREACH (wmDragID *, drag_id, &drag.drag_data.ids) {
|
|
BKE_light_linking_add_receiver_to_collection(
|
|
bmain, &get_collection(), drag_id->id, COLLECTION_LIGHT_LINKING_STATE_INCLUDE);
|
|
}
|
|
|
|
/* It is possible that the light linking collection is also used by the view layer.
|
|
* For this case send a notifier so that the UI is updated for the changes in the collection
|
|
* content. */
|
|
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
|
|
|
|
ED_undo_push(C, "Add to linking collection");
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class ReorderCollectionDropTarget : public BaseCollectionDropTarget {
|
|
const ID &drop_id_;
|
|
|
|
public:
|
|
ReorderCollectionDropTarget(AbstractTreeView &view, Collection &collection, const ID &drop_id)
|
|
: BaseCollectionDropTarget(view, DropBehavior::Reorder, collection), drop_id_(drop_id)
|
|
{
|
|
}
|
|
|
|
std::string drop_tooltip(const DragInfo &drag) const override
|
|
{
|
|
const std::string_view drop_name = std::string_view(drop_id_.name + 2);
|
|
|
|
switch (drag.drop_location) {
|
|
case DropLocation::Into:
|
|
return "Add to linking collection";
|
|
case DropLocation::Before:
|
|
return fmt::format(TIP_("Add to linking collection before {}"), drop_name);
|
|
case DropLocation::After:
|
|
return fmt::format(TIP_("Add to linking collection after {}"), drop_name);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool on_drop(bContext *C, const DragInfo &drag) const override
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
Collection &collection = get_collection();
|
|
const eCollectionLightLinkingState link_state = COLLECTION_LIGHT_LINKING_STATE_INCLUDE;
|
|
|
|
LISTBASE_FOREACH (wmDragID *, drag_id, &drag.drag_data.ids) {
|
|
if (drag_id->id == &drop_id_) {
|
|
continue;
|
|
}
|
|
|
|
BKE_light_linking_unlink_id_from_collection(bmain, &collection, drag_id->id, nullptr);
|
|
|
|
switch (drag.drop_location) {
|
|
case DropLocation::Into:
|
|
BKE_light_linking_add_receiver_to_collection(
|
|
bmain, &collection, drag_id->id, link_state);
|
|
break;
|
|
case DropLocation::Before:
|
|
BKE_light_linking_add_receiver_to_collection_before(
|
|
bmain, &collection, drag_id->id, &drop_id_, link_state);
|
|
break;
|
|
case DropLocation::After:
|
|
BKE_light_linking_add_receiver_to_collection_after(
|
|
bmain, &collection, drag_id->id, &drop_id_, link_state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* It is possible that the light linking collection is also used by the view layer.
|
|
* For this case send a notifier so that the UI is updated for the changes in the collection
|
|
* content. */
|
|
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
|
|
|
|
ED_undo_push(C, "Add to linking collection");
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class ItemDragController : public AbstractViewItemDragController {
|
|
ID &id_;
|
|
|
|
public:
|
|
explicit ItemDragController(AbstractView &view, ID &id)
|
|
: AbstractViewItemDragController(view), id_(id)
|
|
{
|
|
}
|
|
|
|
eWM_DragDataType get_drag_type() const override
|
|
{
|
|
return WM_DRAG_ID;
|
|
}
|
|
|
|
void *create_drag_data() const override
|
|
{
|
|
return static_cast<void *>(&id_);
|
|
}
|
|
};
|
|
|
|
class CollectionViewItem : public BasicTreeViewItem {
|
|
uiLayout &context_layout_;
|
|
Collection &collection_;
|
|
|
|
ID &id_;
|
|
CollectionLightLinking &collection_light_linking_;
|
|
|
|
public:
|
|
CollectionViewItem(uiLayout &context_layout,
|
|
Collection &collection,
|
|
ID &id,
|
|
CollectionLightLinking &collection_light_linking,
|
|
const BIFIconID icon)
|
|
: BasicTreeViewItem(id.name + 2, icon),
|
|
context_layout_(context_layout),
|
|
collection_(collection),
|
|
id_(id),
|
|
collection_light_linking_(collection_light_linking)
|
|
{
|
|
}
|
|
|
|
void build_row(uiLayout &row) override
|
|
{
|
|
if (is_active()) {
|
|
PointerRNA id_ptr = RNA_id_pointer_create(&id_);
|
|
PointerRNA collection_ptr = RNA_id_pointer_create(&collection_.id);
|
|
|
|
uiLayoutSetContextPointer(&context_layout_, "id", &id_ptr);
|
|
uiLayoutSetContextPointer(&context_layout_, "collection", &collection_ptr);
|
|
}
|
|
|
|
add_label(row);
|
|
|
|
uiLayout *sub = uiLayoutRow(&row, true);
|
|
uiLayoutSetPropDecorate(sub, false);
|
|
|
|
build_state_button(*sub);
|
|
}
|
|
|
|
std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
|
|
{
|
|
return std::make_unique<ItemDragController>(get_tree_view(), id_);
|
|
}
|
|
|
|
std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
|
|
{
|
|
return std::make_unique<ReorderCollectionDropTarget>(get_tree_view(), collection_, id_);
|
|
}
|
|
|
|
private:
|
|
int get_state_icon() const
|
|
{
|
|
switch (collection_light_linking_.link_state) {
|
|
case COLLECTION_LIGHT_LINKING_STATE_INCLUDE:
|
|
return ICON_CHECKBOX_HLT;
|
|
case COLLECTION_LIGHT_LINKING_STATE_EXCLUDE:
|
|
return ICON_CHECKBOX_DEHLT;
|
|
}
|
|
BLI_assert_unreachable();
|
|
return ICON_NONE;
|
|
}
|
|
|
|
static void link_state_toggle(CollectionLightLinking &collection_light_linking)
|
|
{
|
|
switch (collection_light_linking.link_state) {
|
|
case COLLECTION_LIGHT_LINKING_STATE_INCLUDE:
|
|
collection_light_linking.link_state = COLLECTION_LIGHT_LINKING_STATE_EXCLUDE;
|
|
return;
|
|
case COLLECTION_LIGHT_LINKING_STATE_EXCLUDE:
|
|
collection_light_linking.link_state = COLLECTION_LIGHT_LINKING_STATE_INCLUDE;
|
|
return;
|
|
}
|
|
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
void build_state_button(uiLayout &row)
|
|
{
|
|
uiBlock *block = uiLayoutGetBlock(&row);
|
|
const int icon = get_state_icon();
|
|
|
|
PointerRNA collection_light_linking_ptr = RNA_pointer_create(
|
|
&collection_.id, &RNA_CollectionLightLinking, &collection_light_linking_);
|
|
|
|
uiBut *button = uiDefIconButR(block,
|
|
UI_BTYPE_BUT,
|
|
0,
|
|
icon,
|
|
0,
|
|
0,
|
|
UI_UNIT_X,
|
|
UI_UNIT_Y,
|
|
&collection_light_linking_ptr,
|
|
"link_state",
|
|
0,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
0.0f,
|
|
nullptr);
|
|
|
|
UI_but_func_set(button, [&collection_light_linking = collection_light_linking_](bContext &) {
|
|
link_state_toggle(collection_light_linking);
|
|
});
|
|
}
|
|
};
|
|
|
|
class CollectionView : public AbstractTreeView {
|
|
uiLayout &context_layout_;
|
|
Collection &collection_;
|
|
|
|
public:
|
|
CollectionView(uiLayout &context_layout, Collection &collection)
|
|
: context_layout_(context_layout), collection_(collection)
|
|
{
|
|
}
|
|
|
|
void build_tree() override
|
|
{
|
|
LISTBASE_FOREACH (CollectionChild *, collection_child, &collection_.children) {
|
|
Collection *child_collection = collection_child->collection;
|
|
add_tree_item<CollectionViewItem>(context_layout_,
|
|
collection_,
|
|
child_collection->id,
|
|
collection_child->light_linking,
|
|
ICON_OUTLINER_COLLECTION);
|
|
}
|
|
|
|
LISTBASE_FOREACH (CollectionObject *, collection_object, &collection_.gobject) {
|
|
Object *child_object = collection_object->ob;
|
|
add_tree_item<CollectionViewItem>(context_layout_,
|
|
collection_,
|
|
child_object->id,
|
|
collection_object->light_linking,
|
|
ICON_OBJECT_DATA);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<DropTargetInterface> create_drop_target() override
|
|
{
|
|
return std::make_unique<InsertCollectionDropTarget>(*this, collection_);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
} // namespace blender::ui::light_linking
|
|
|
|
void uiTemplateLightLinkingCollection(uiLayout *layout,
|
|
uiLayout *context_layout,
|
|
PointerRNA *ptr,
|
|
const char *propname)
|
|
{
|
|
if (!ptr->data) {
|
|
return;
|
|
}
|
|
|
|
PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
|
|
if (!prop) {
|
|
printf(
|
|
"%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname);
|
|
return;
|
|
}
|
|
|
|
if (RNA_property_type(prop) != PROP_POINTER) {
|
|
printf("%s: expected pointer property for %s.%s\n",
|
|
__func__,
|
|
RNA_struct_identifier(ptr->type),
|
|
propname);
|
|
return;
|
|
}
|
|
|
|
const PointerRNA collection_ptr = RNA_property_pointer_get(ptr, prop);
|
|
if (!collection_ptr.data) {
|
|
return;
|
|
}
|
|
if (collection_ptr.type != &RNA_Collection) {
|
|
printf("%s: expected collection pointer property for %s.%s\n",
|
|
__func__,
|
|
RNA_struct_identifier(ptr->type),
|
|
propname);
|
|
return;
|
|
}
|
|
|
|
Collection *collection = static_cast<Collection *>(collection_ptr.data);
|
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
|
|
|
blender::ui::AbstractTreeView *tree_view = UI_block_add_view(
|
|
*block,
|
|
"Light Linking Collection Tree View",
|
|
std::make_unique<blender::ui::light_linking::CollectionView>(*context_layout, *collection));
|
|
tree_view->set_min_rows(3);
|
|
|
|
blender::ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
|
}
|