Files
test/source/blender/editors/interface/interface_template_light_linking.cc
Hans Goudey 3d57bc4397 Cleanup: Move several blenkernel headers to C++
Mostly focus on areas where we're already using C++ features,
where combining C and C++ APIs is getting in the way.

Pull Request: https://projects.blender.org/blender/blender/pulls/114972
2023-11-16 11:41:55 +01:00

408 lines
12 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.hh"
#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 CollectionDropTarget {
Collection &collection_;
public:
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const
{
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;
}
CollectionDropTarget(Collection &collection) : collection_(collection) {}
Collection &get_collection() const
{
return collection_;
}
};
/**
* Drop target for the view (when dropping into empty space of the view), not for an item.
*/
class InsertCollectionDropTarget : public DropTargetInterface {
CollectionDropTarget collection_target_;
public:
InsertCollectionDropTarget(Collection &collection) : collection_target_(collection) {}
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
{
return collection_target_.can_drop(drag, r_disabled_hint);
}
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,
&collection_target_.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 TreeViewItemDropTarget {
CollectionDropTarget collection_target_;
const ID &drop_id_;
public:
ReorderCollectionDropTarget(AbstractTreeViewItem &item,
Collection &collection,
const ID &drop_id)
: TreeViewItemDropTarget(item, DropBehavior::Reorder),
collection_target_(collection),
drop_id_(drop_id)
{
}
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
{
return collection_target_.can_drop(drag, r_disabled_hint);
}
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 = collection_target_.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>(*this, 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>(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);
}