Light linking: Allow re-ordering in the light linking collections #112849

Merged
Sergey Sharybin merged 2 commits from Sergey/blender:light_linking_reorder into main 2023-09-25 18:00:37 +02:00
3 changed files with 349 additions and 24 deletions

View File

@ -69,6 +69,18 @@ void BKE_light_linking_add_receiver_to_collection(struct Main *bmain,
struct Collection *collection,
struct ID *receiver,
const eCollectionLightLinkingState link_state);
void BKE_light_linking_add_receiver_to_collection_before(
struct Main *bmain,
struct Collection *collection,
struct ID *receiver,
const struct ID *before,
const eCollectionLightLinkingState link_state);
void BKE_light_linking_add_receiver_to_collection_after(
struct Main *bmain,
struct Collection *collection,
struct ID *receiver,
const struct ID *after,
const eCollectionLightLinkingState link_state);
/* Remove the given ID from the light or shadow linking collection of the given object.
*

View File

@ -133,6 +133,29 @@ void BKE_light_linking_collection_assign(Main *bmain,
DEG_relations_tag_update(bmain);
}
static CollectionObject *find_collection_object(const Collection *collection, const Object *object)
{
LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) {
if (collection_object->ob == object) {
return collection_object;
}
}
return nullptr;
}
static CollectionChild *find_collection_child(const Collection *collection,
const Collection *child)
{
LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) {
if (collection_child->collection == child) {
return collection_child;
}
}
return nullptr;
}
/* Add object to the light linking collection and return corresponding CollectionLightLinking
* settings.
*
@ -144,15 +167,14 @@ static CollectionLightLinking *light_linking_collection_add_object(Main *bmain,
{
BKE_collection_object_add(bmain, collection, object);
LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) {
if (collection_object->ob == object) {
return &collection_object->light_linking;
}
CollectionObject *collection_object = find_collection_object(collection, object);
if (!collection_object) {
BLI_assert_msg(0, "Object was not found after added to the light linking collection");
return nullptr;
}
BLI_assert_msg(0, "Object was not found after added to the light linking collection");
return nullptr;
return &collection_object->light_linking;
}
/* Add child collection to the light linking collection and return corresponding
@ -166,15 +188,14 @@ static CollectionLightLinking *light_linking_collection_add_collection(Main *bma
{
BKE_collection_child_add(bmain, collection, child);
LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) {
if (collection_child->collection == child) {
return &collection_child->light_linking;
}
CollectionChild *collection_child = find_collection_child(collection, child);
if (!collection_child) {
BLI_assert_msg(0, "Collection was not found after added to the light linking collection");
return nullptr;
}
BLI_assert_msg(0, "Collection was not found after added to the light linking collection");
return nullptr;
return &collection_child->light_linking;
}
void BKE_light_linking_add_receiver_to_collection(Main *bmain,
@ -213,6 +234,182 @@ void BKE_light_linking_add_receiver_to_collection(Main *bmain,
DEG_relations_tag_update(bmain);
}
static void order_collection_receiver_before(Collection *collection,
Collection *receiver,
const ID *before)
{
CollectionChild *receiver_collection_child = find_collection_child(collection, receiver);
if (!receiver_collection_child) {
BLI_assert_msg(0, "Receiver child was not found after adding collection to light linking");
return;
}
const ID_Type before_id_type = GS(before->name);
if (before_id_type != ID_GR) {
/* Adding before object: move the collection to the very bottom.
* This is as far to the bottom as the receiver can be in the flattened list of the collection.
*/
BLI_remlink(&collection->children, receiver_collection_child);
BLI_addtail(&collection->children, receiver_collection_child);
return;
}
CollectionChild *before_collection_child = find_collection_child(
collection, reinterpret_cast<const Collection *>(before));
if (!before_collection_child) {
BLI_assert_msg(0, "Before child was not found");
return;
}
BLI_remlink(&collection->children, receiver_collection_child);
BLI_insertlinkbefore(&collection->children, before_collection_child, receiver_collection_child);
}
static void order_collection_receiver_after(Collection *collection,
Collection *receiver,
const ID *after)
{
CollectionChild *receiver_collection_child = find_collection_child(collection, receiver);
if (!receiver_collection_child) {
BLI_assert_msg(0, "Receiver child was not found after adding collection to light linking");
return;
}
const ID_Type after_id_type = GS(after->name);
if (after_id_type != ID_GR) {
/* Adding before object: move the collection to the very bottom.
* This is as far to the bottom as the receiver can be in the flattened list of the collection.
*/
BLI_remlink(&collection->children, receiver_collection_child);
BLI_addtail(&collection->children, receiver_collection_child);
return;
}
CollectionChild *after_collection_child = find_collection_child(
collection, reinterpret_cast<const Collection *>(after));
if (!after_collection_child) {
BLI_assert_msg(0, "After child was not found");
return;
}
BLI_remlink(&collection->children, receiver_collection_child);
BLI_insertlinkafter(&collection->children, after_collection_child, receiver_collection_child);
}
static void order_object_receiver_before(Collection *collection,
Object *receiver,
const ID *before)
{
CollectionObject *receiver_collection_object = find_collection_object(collection, receiver);
if (!receiver_collection_object) {
BLI_assert_msg(
0, "Receiver collection object was not found after adding collection to light linking");
return;
}
const ID_Type before_id_type = GS(before->name);
if (before_id_type != ID_OB) {
/* Adding before collection: move the receiver to the very beginning of the child objects list.
* This is as close to the top of the flattened list of the collection content the object can
* possibly be. */
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_addhead(&collection->gobject, receiver_collection_object);
return;
}
CollectionObject *before_collection_object = find_collection_object(
collection, reinterpret_cast<const Object *>(before));
if (!before_collection_object) {
BLI_assert_msg(0, "Before collection object was not found");
return;
}
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_insertlinkbefore(&collection->gobject, before_collection_object, receiver_collection_object);
}
static void order_object_receiver_after(Collection *collection, Object *receiver, const ID *after)
{
CollectionObject *receiver_collection_object = find_collection_object(collection, receiver);
if (!receiver_collection_object) {
BLI_assert_msg(
0, "Receiver collection object was not found after adding collection to light linking");
return;
}
const ID_Type after_id_type = GS(after->name);
if (after_id_type != ID_OB) {
/* Adding after collection: move the receiver to the very beginning of the child objects list.
* This is as close to the top of the flattened list of the collection content the object can
* possibly be. */
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_addhead(&collection->gobject, receiver_collection_object);
return;
}
CollectionObject *after_collection_object = find_collection_object(
collection, reinterpret_cast<const Object *>(after));
if (!after_collection_object) {
BLI_assert_msg(0, "After collection object was not found");
return;
}
BLI_remlink(&collection->gobject, receiver_collection_object);
BLI_insertlinkafter(&collection->gobject, after_collection_object, receiver_collection_object);
}
void BKE_light_linking_add_receiver_to_collection_before(
Main *bmain,
Collection *collection,
ID *receiver,
const ID *before,
const eCollectionLightLinkingState link_state)
{
BLI_assert(before);
BKE_light_linking_add_receiver_to_collection(bmain, collection, receiver, link_state);
if (!before) {
return;
}
const ID_Type id_type = GS(receiver->name);
if (id_type == ID_OB) {
order_object_receiver_before(collection, reinterpret_cast<Object *>(receiver), before);
}
else if (id_type == ID_GR) {
order_collection_receiver_before(collection, reinterpret_cast<Collection *>(receiver), before);
}
}
void BKE_light_linking_add_receiver_to_collection_after(
Main *bmain,
Collection *collection,
ID *receiver,
const ID *after,
const eCollectionLightLinkingState link_state)
{
BLI_assert(after);
BKE_light_linking_add_receiver_to_collection(bmain, collection, receiver, link_state);
if (!after) {
return;
}
const ID_Type id_type = GS(receiver->name);
if (id_type == ID_OB) {
order_object_receiver_after(collection, reinterpret_cast<Object *>(receiver), after);
}
else if (id_type == ID_GR) {
order_collection_receiver_after(collection, reinterpret_cast<Collection *>(receiver), after);
}
}
bool BKE_light_linking_unlink_id_from_collection(Main *bmain,
Collection *collection,
ID *id,

View File

@ -11,6 +11,8 @@
#include <cstdio>
#include <memory>
#include <fmt/format.h>
#include "BLT_translation.h"
#include "DNA_collection_types.h"
@ -34,12 +36,10 @@ namespace blender::ui::light_linking {
namespace {
class CollectionDropTarget : public DropTargetInterface {
class BaseCollectionDropTarget : public TreeViewItemDropTarget {
Collection &collection_;
public:
CollectionDropTarget(Collection &collection) : collection_(collection) {}
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
{
if (drag.type != WM_DRAG_ID) {
@ -62,9 +62,28 @@ class CollectionDropTarget : public DropTargetInterface {
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 light linking collection");
return TIP_("Add to linking collection");

Maybe "Add to light linking collection" > "Add to linking collection" to avoid confusion with the shadow linking case?

Maybe "Add to light linking collection" > "Add to linking collection" to avoid confusion with the shadow linking case?
}
bool on_drop(bContext *C, const DragInfo &drag) const override
@ -74,7 +93,7 @@ class CollectionDropTarget : public DropTargetInterface {
LISTBASE_FOREACH (wmDragID *, drag_id, &drag.drag_data.ids) {
BKE_light_linking_add_receiver_to_collection(
bmain, &collection_, drag_id->id, COLLECTION_LIGHT_LINKING_STATE_INCLUDE);
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.
@ -82,17 +101,104 @@ class CollectionDropTarget : public DropTargetInterface {
* content. */
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
ED_undo_push(C, "Add to light linking collection");
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);

It would be nice if reordering would show just "Move before" and "Move after" rather than "Add".

It would be nice if reordering would show just "Move before" and "Move after" rather than "Add".

I was thinking about that. Thing is: it is only correct to use "Move" when the drag source originates from the linking collection. But it is possible to drag ID from outliner and insert it before/after existing item in the collection, and that's where the "Move" sounds a bit odd.

Currently there is no way to attach extra flag to the drag data used for the ID dragging. We could add special eWM_DragDataType so that we can distinguish origin from the drop_tooltip to generate proper tooltip, but it does not seem to worth the trouble.

Maybe there is an easy way to distinguish move and add within existing framework, but I did not find it yet.

I was thinking about that. Thing is: it is only correct to use "Move" when the drag source originates from the linking collection. But it is possible to drag ID from outliner and insert it before/after existing item in the collection, and that's where the "Move" sounds a bit odd. Currently there is no way to attach extra flag to the drag data used for the ID dragging. We could add special `eWM_DragDataType` so that we can distinguish origin from the `drop_tooltip` to generate proper tooltip, but it does not seem to worth the trouble. Maybe there is an easy way to distinguish move and add within existing framework, but I did not find it yet.
}
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_ = nullptr;
ID &id_;
CollectionLightLinking &collection_light_linking_;
public:
@ -104,7 +210,7 @@ class CollectionViewItem : public BasicTreeViewItem {
: BasicTreeViewItem(id.name + 2, icon),
context_layout_(context_layout),
collection_(collection),
id_(&id),
id_(id),
collection_light_linking_(collection_light_linking)
{
}
@ -112,7 +218,7 @@ class CollectionViewItem : public BasicTreeViewItem {
void build_row(uiLayout &row) override
{
if (is_active()) {
PointerRNA id_ptr = RNA_id_pointer_create(id_);
PointerRNA id_ptr = RNA_id_pointer_create(&id_);
PointerRNA collection_ptr = RNA_id_pointer_create(&collection_.id);
uiLayoutSetContextPointer(&context_layout_, "id", &id_ptr);
@ -127,6 +233,16 @@ class CollectionViewItem : public BasicTreeViewItem {
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
{
@ -219,7 +335,7 @@ class CollectionView : public AbstractTreeView {
std::unique_ptr<DropTargetInterface> create_drop_target() override
{
return std::make_unique<CollectionDropTarget>(collection_);
return std::make_unique<InsertCollectionDropTarget>(*this, collection_);
}
};