diff options
Diffstat (limited to 'source/blender/editors/space_outliner/outliner_dragdrop.c')
-rw-r--r-- | source/blender/editors/space_outliner/outliner_dragdrop.c | 517 |
1 files changed, 496 insertions, 21 deletions
diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.c b/source/blender/editors/space_outliner/outliner_dragdrop.c index 46a5f90f6c2..865e06bf0b6 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.c +++ b/source/blender/editors/space_outliner/outliner_dragdrop.c @@ -26,7 +26,9 @@ #include "MEM_guardedalloc.h" #include "DNA_collection_types.h" +#include "DNA_constraint_types.h" #include "DNA_material_types.h" +#include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "DNA_space_types.h" @@ -36,6 +38,7 @@ #include "BLT_translation.h" #include "BKE_collection.h" +#include "BKE_constraint.h" #include "BKE_context.h" #include "BKE_layer.h" #include "BKE_lib_id.h" @@ -44,6 +47,7 @@ #include "BKE_object.h" #include "BKE_report.h" #include "BKE_scene.h" +#include "BKE_shader_fx.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" @@ -65,6 +69,8 @@ #include "outliner_intern.h" +static Collection *collection_parent_from_ID(ID *id); + /* ******************** Drop Target Find *********************** */ static TreeElement *outliner_dropzone_element(TreeElement *te, @@ -79,8 +85,8 @@ static TreeElement *outliner_dropzone_element(TreeElement *te, } /* Not it. Let's look at its children. */ if (children && (TREESTORE(te)->flag & TSE_CLOSED) == 0 && (te->subtree.first)) { - for (te = te->subtree.first; te; te = te->next) { - TreeElement *te_valid = outliner_dropzone_element(te, fmval, children); + LISTBASE_FOREACH (TreeElement *, te_sub, &te->subtree) { + TreeElement *te_valid = outliner_dropzone_element(te_sub, fmval, children); if (te_valid) { return te_valid; } @@ -94,9 +100,7 @@ static TreeElement *outliner_dropzone_find(const SpaceOutliner *space_outliner, const float fmval[2], const bool children) { - TreeElement *te; - - for (te = space_outliner->tree.first; te; te = te->next) { + LISTBASE_FOREACH (TreeElement *, te, &space_outliner->tree) { TreeElement *te_valid = outliner_dropzone_element(te, fmval, children); if (te_valid) { return te_valid; @@ -146,7 +150,8 @@ static TreeElement *outliner_drop_insert_find(bContext *C, const float margin = UI_UNIT_Y * (1.0f / 4); if (view_mval[1] < (te_hovered->ys + margin)) { - if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner)) { + if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner) && + !BLI_listbase_is_empty(&te_hovered->subtree)) { /* inserting after a open item means we insert into it, but as first child */ if (BLI_listbase_is_empty(&te_hovered->subtree)) { *r_insert_type = TE_INSERT_INTO; @@ -183,20 +188,37 @@ static TreeElement *outliner_drop_insert_find(bContext *C, return NULL; } -static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te, - TreeElement **r_te) +typedef bool (*CheckTypeFn)(TreeElement *te); + +static TreeElement *outliner_data_from_tree_element_and_parents(CheckTypeFn check_type, + TreeElement *te) { while (te != NULL) { - Collection *collection = outliner_collection_from_tree_element(te); - if (collection) { - *r_te = te; - return collection; + if (check_type(te)) { + return te; } te = te->parent; } return NULL; } +static bool is_collection_element(TreeElement *te) +{ + return outliner_is_collection_tree_element(te); +} + +static bool is_object_element(TreeElement *te) +{ + TreeStoreElem *tselem = TREESTORE(te); + return tselem->type == 0 && te->idcode == ID_OB; +} + +static bool is_pchan_element(TreeElement *te) +{ + TreeStoreElem *tselem = TREESTORE(te); + return tselem->type == TSE_POSE_CHANNEL; +} + static TreeElement *outliner_drop_insert_collection_find(bContext *C, const wmEvent *event, TreeElementInsertType *r_insert_type) @@ -206,11 +228,12 @@ static TreeElement *outliner_drop_insert_collection_find(bContext *C, return NULL; } - TreeElement *collection_te; - Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te); - if (!collection) { + TreeElement *collection_te = outliner_data_from_tree_element_and_parents(is_collection_element, + te); + if (!collection_te) { return NULL; } + Collection *collection = outliner_collection_from_tree_element(collection_te); if (collection_te != te) { *r_insert_type = TE_INSERT_INTO; @@ -224,6 +247,30 @@ static TreeElement *outliner_drop_insert_collection_find(bContext *C, return collection_te; } +static int outliner_get_insert_index(TreeElement *drag_te, + TreeElement *drop_te, + TreeElementInsertType insert_type, + ListBase *listbase) +{ + /* Find the element to insert after. NULL is the start of the list. */ + if (drag_te->index < drop_te->index) { + if (insert_type == TE_INSERT_BEFORE) { + drop_te = drop_te->prev; + } + } + else { + if (insert_type == TE_INSERT_AFTER) { + drop_te = drop_te->next; + } + } + + if (drop_te == NULL) { + return 0; + } + + return BLI_findindex(listbase, drop_te->directdata); +} + /* ******************** Parent Drop Operator *********************** */ static bool parent_drop_allowed(TreeElement *te, Object *potential_child) @@ -594,6 +641,7 @@ static int material_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEve BKE_object_material_assign(bmain, ob, ma, ob->totcol + 1, BKE_MAT_ASSIGN_USERPREF); + WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C)); WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); @@ -616,6 +664,417 @@ void OUTLINER_OT_material_drop(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; } +/* ******************** Data Stack Drop Operator *********************** */ + +/* A generic operator to allow drag and drop for modifiers, constraints, + * and shader effects which all share the same UI stack layout. + * + * The following operations are allowed: + * - Reordering within an object. + * - Copying a single modifier/constraint/effect to another object. + * - Copying (linking) an object's modifiers/constraints/effects to another. */ + +typedef enum eDataStackDropAction { + DATA_STACK_DROP_REORDER, + DATA_STACK_DROP_COPY, + DATA_STACK_DROP_LINK, +} eDataStackDropAction; + +typedef struct StackDropData { + Object *ob_parent; + bPoseChannel *pchan_parent; + TreeStoreElem *drag_tselem; + void *drag_directdata; + int drag_index; + + eDataStackDropAction drop_action; + TreeElement *drop_te; + TreeElementInsertType insert_type; +} StackDropData; + +static void datastack_drop_data_init(wmDrag *drag, + Object *ob, + bPoseChannel *pchan, + TreeElement *te, + TreeStoreElem *tselem, + void *directdata) +{ + StackDropData *drop_data = MEM_callocN(sizeof(*drop_data), "datastack drop data"); + + drop_data->ob_parent = ob; + drop_data->pchan_parent = pchan; + drop_data->drag_tselem = tselem; + drop_data->drag_directdata = directdata; + drop_data->drag_index = te->index; + + drag->poin = drop_data; + drag->flags |= WM_DRAG_FREE_DATA; +} + +static bool datastack_drop_init(bContext *C, const wmEvent *event, StackDropData *drop_data) +{ + if (!ELEM(drop_data->drag_tselem->type, + TSE_MODIFIER, + TSE_MODIFIER_BASE, + TSE_CONSTRAINT, + TSE_CONSTRAINT_BASE, + TSE_GPENCIL_EFFECT, + TSE_GPENCIL_EFFECT_BASE)) { + return false; + } + + TreeElement *te_target = outliner_drop_insert_find(C, event, &drop_data->insert_type); + if (!te_target) { + return false; + } + TreeStoreElem *tselem_target = TREESTORE(te_target); + + if (drop_data->drag_tselem == tselem_target) { + return false; + } + + Object *ob = NULL; + TreeElement *object_te = outliner_data_from_tree_element_and_parents(is_object_element, + te_target); + if (object_te) { + ob = (Object *)TREESTORE(object_te)->id; + } + + bPoseChannel *pchan = NULL; + TreeElement *pchan_te = outliner_data_from_tree_element_and_parents(is_pchan_element, te_target); + if (pchan_te) { + pchan = (bPoseChannel *)pchan_te->directdata; + } + if (pchan) { + ob = NULL; + } + + if (ob && ID_IS_LINKED(&ob->id)) { + return false; + } + + /* Drag a base for linking. */ + if (ELEM(drop_data->drag_tselem->type, + TSE_MODIFIER_BASE, + TSE_CONSTRAINT_BASE, + TSE_GPENCIL_EFFECT_BASE)) { + drop_data->insert_type = TE_INSERT_INTO; + drop_data->drop_action = DATA_STACK_DROP_LINK; + + if (pchan && pchan != drop_data->pchan_parent) { + drop_data->drop_te = pchan_te; + tselem_target = TREESTORE(pchan_te); + } + else if (ob && ob != drop_data->ob_parent) { + drop_data->drop_te = object_te; + tselem_target = TREESTORE(object_te); + } + else { + return false; + } + } + else if (ob || pchan) { + /* Drag a single item. */ + if (pchan && pchan != drop_data->pchan_parent) { + drop_data->insert_type = TE_INSERT_INTO; + drop_data->drop_action = DATA_STACK_DROP_COPY; + drop_data->drop_te = pchan_te; + tselem_target = TREESTORE(pchan_te); + } + else if (ob && ob != drop_data->ob_parent) { + drop_data->insert_type = TE_INSERT_INTO; + drop_data->drop_action = DATA_STACK_DROP_COPY; + drop_data->drop_te = object_te; + tselem_target = TREESTORE(object_te); + } + else if (tselem_target->type == drop_data->drag_tselem->type) { + if (drop_data->insert_type == TE_INSERT_INTO) { + return false; + } + drop_data->drop_action = DATA_STACK_DROP_REORDER; + drop_data->drop_te = te_target; + } + else { + return false; + } + } + else { + return false; + } + + return true; +} + +/* Ensure that grease pencil and object data remain separate. */ +static bool datastack_drop_are_types_valid(StackDropData *drop_data) +{ + TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); + Object *ob_parent = drop_data->ob_parent; + Object *ob_dst = (Object *)tselem->id; + + /* Don't allow data to be moved between objects and bones. */ + if (tselem->type == TSE_CONSTRAINT) { + } + else if ((drop_data->pchan_parent && tselem->type != TSE_POSE_CHANNEL) || + (!drop_data->pchan_parent && tselem->type == TSE_POSE_CHANNEL)) { + return false; + } + + switch (drop_data->drag_tselem->type) { + case TSE_MODIFIER_BASE: + case TSE_MODIFIER: + if (ob_parent->type == OB_GPENCIL) { + return ob_dst->type == OB_GPENCIL; + } + else if (ob_parent->type != OB_GPENCIL) { + return ob_dst->type != OB_GPENCIL; + } + break; + case TSE_CONSTRAINT_BASE: + case TSE_CONSTRAINT: + + break; + case TSE_GPENCIL_EFFECT_BASE: + case TSE_GPENCIL_EFFECT: + return ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL; + break; + } + + return true; +} + +static bool datastack_drop_poll(bContext *C, + wmDrag *drag, + const wmEvent *event, + const char **r_tooltip) +{ + if (drag->type != WM_DRAG_DATASTACK) { + return false; + } + + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + ARegion *region = CTX_wm_region(C); + bool changed = outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false); + + StackDropData *drop_data = drag->poin; + if (!drop_data) { + return false; + } + + if (!datastack_drop_init(C, event, drop_data)) { + return false; + } + + if (!datastack_drop_are_types_valid(drop_data)) { + return false; + } + + TreeStoreElem *tselem_target = TREESTORE(drop_data->drop_te); + switch (drop_data->insert_type) { + case TE_INSERT_BEFORE: + tselem_target->flag |= TSE_DRAG_BEFORE; + break; + case TE_INSERT_AFTER: + tselem_target->flag |= TSE_DRAG_AFTER; + break; + case TE_INSERT_INTO: + tselem_target->flag |= TSE_DRAG_INTO; + break; + } + + switch (drop_data->drop_action) { + case DATA_STACK_DROP_REORDER: + *r_tooltip = TIP_("Reorder"); + break; + case DATA_STACK_DROP_COPY: + if (drop_data->pchan_parent) { + *r_tooltip = TIP_("Copy to bone"); + } + else { + *r_tooltip = TIP_("Copy to object"); + } + break; + case DATA_STACK_DROP_LINK: + if (drop_data->pchan_parent) { + *r_tooltip = TIP_("Link all to bone"); + } + else { + *r_tooltip = TIP_("Link all to object"); + } + break; + } + + if (changed) { + ED_region_tag_redraw_no_rebuild(region); + } + + return true; +} + +static void datastack_drop_link(bContext *C, StackDropData *drop_data) +{ + Main *bmain = CTX_data_main(C); + TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); + Object *ob_dst = (Object *)tselem->id; + + switch (drop_data->drag_tselem->type) { + case TSE_MODIFIER_BASE: + ED_object_modifier_link(C, ob_dst, drop_data->ob_parent); + break; + case TSE_CONSTRAINT_BASE: { + ListBase *src; + + if (drop_data->pchan_parent) { + src = &drop_data->pchan_parent->constraints; + } + else { + src = &drop_data->ob_parent->constraints; + } + + ListBase *dst; + if (tselem->type == TSE_POSE_CHANNEL) { + bPoseChannel *pchan = (bPoseChannel *)drop_data->drop_te->directdata; + dst = &pchan->constraints; + } + else { + dst = &ob_dst->constraints; + } + + ED_object_constraint_link(bmain, ob_dst, dst, src); + break; + } + case TSE_GPENCIL_EFFECT_BASE: + if (ob_dst->type != OB_GPENCIL) { + return; + } + + ED_object_shaderfx_link(ob_dst, drop_data->ob_parent); + break; + } +} + +static void datastack_drop_copy(bContext *C, StackDropData *drop_data) +{ + Main *bmain = CTX_data_main(C); + + TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); + Object *ob_dst = (Object *)tselem->id; + + switch (drop_data->drag_tselem->type) { + case TSE_MODIFIER: + if (drop_data->ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL) { + ED_object_gpencil_modifier_copy_to_object(ob_dst, drop_data->drag_directdata); + } + else if (drop_data->ob_parent->type != OB_GPENCIL && ob_dst->type != OB_GPENCIL) { + ED_object_modifier_copy_to_object( + C, ob_dst, drop_data->ob_parent, drop_data->drag_directdata); + } + break; + case TSE_CONSTRAINT: + if (tselem->type == TSE_POSE_CHANNEL) { + ED_object_constraint_copy_for_pose( + bmain, ob_dst, drop_data->drop_te->directdata, drop_data->drag_directdata); + } + else { + ED_object_constraint_copy_for_object(bmain, ob_dst, drop_data->drag_directdata); + } + break; + case TSE_GPENCIL_EFFECT: { + if (ob_dst->type != OB_GPENCIL) { + return; + } + + ED_object_shaderfx_copy(ob_dst, drop_data->drag_directdata); + break; + } + } +} + +static void datastack_drop_reorder(bContext *C, ReportList *reports, StackDropData *drop_data) +{ + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + + TreeElement *drag_te = outliner_find_tree_element(&space_outliner->tree, drop_data->drag_tselem); + if (!drag_te) { + return; + } + + TreeElement *drop_te = drop_data->drop_te; + TreeElementInsertType insert_type = drop_data->insert_type; + + Object *ob = drop_data->ob_parent; + + int index = 0; + switch (drop_data->drag_tselem->type) { + case TSE_MODIFIER: + if (ob->type == OB_GPENCIL) { + index = outliner_get_insert_index( + drag_te, drop_te, insert_type, &ob->greasepencil_modifiers); + ED_object_gpencil_modifier_move_to_index(reports, ob, drop_data->drag_directdata, index); + } + else if (ob->type != OB_GPENCIL) { + index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->modifiers); + ED_object_modifier_move_to_index(reports, ob, drop_data->drag_directdata, index); + } + break; + case TSE_CONSTRAINT: + if (drop_data->pchan_parent) { + index = outliner_get_insert_index( + drag_te, drop_te, insert_type, &drop_data->pchan_parent->constraints); + } + else { + index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->constraints); + } + ED_object_constraint_move_to_index(ob, drop_data->drag_directdata, index); + + break; + case TSE_GPENCIL_EFFECT: + index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->shader_fx); + ED_object_shaderfx_move_to_index(reports, ob, drop_data->drag_directdata, index); + } +} + +static int datastack_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED; + } + + ListBase *lb = event->customdata; + wmDrag *drag = lb->first; + StackDropData *drop_data = drag->poin; + + switch (drop_data->drop_action) { + case DATA_STACK_DROP_LINK: + datastack_drop_link(C, drop_data); + break; + case DATA_STACK_DROP_COPY: + datastack_drop_copy(C, drop_data); + break; + case DATA_STACK_DROP_REORDER: + datastack_drop_reorder(C, op->reports, drop_data); + break; + } + + return OPERATOR_FINISHED; +} + +void OUTLINER_OT_datastack_drop(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Data Stack Drop"; + ot->description = "Copy or reorder modifiers, constraints, and effects"; + ot->idname = "OUTLINER_OT_datastack_drop"; + + /* api callbacks */ + ot->invoke = datastack_drop_invoke; + + ot->poll = ED_operator_outliner_active; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; +} + /* ******************** Collection Drop Operator *********************** */ typedef struct CollectionDrop { @@ -882,7 +1341,8 @@ static int outliner_item_drag_drop_invoke(bContext *C, return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } - TreeElementIcon data = tree_element_get_icon(TREESTORE(te), te); + TreeStoreElem *tselem = TREESTORE(te); + TreeElementIcon data = tree_element_get_icon(tselem, te); if (!data.drag_id) { return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); } @@ -908,15 +1368,29 @@ static int outliner_item_drag_drop_invoke(bContext *C, WM_operator_properties_free(&op_ptr); } - wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP); + const bool use_datastack_drag = ELEM(tselem->type, + TSE_MODIFIER, + TSE_MODIFIER_BASE, + TSE_CONSTRAINT, + TSE_CONSTRAINT_BASE, + TSE_GPENCIL_EFFECT, + TSE_GPENCIL_EFFECT_BASE); + + const int wm_drag_type = use_datastack_drag ? WM_DRAG_DATASTACK : WM_DRAG_ID; + wmDrag *drag = WM_event_start_drag(C, data.icon, wm_drag_type, NULL, 0.0, WM_DRAG_NOP); - if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { + if (use_datastack_drag) { + TreeElement *te_bone = NULL; + bPoseChannel *pchan = outliner_find_parent_bone(te, &te_bone); + datastack_drop_data_init(drag, (Object *)tselem->id, pchan, te, tselem, te->directdata); + } + else if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { /* For collections and objects we cheat and drag all selected. */ /* Only drag element under mouse if it was not selected before. */ - if ((TREESTORE(te)->flag & TSE_SELECTED) == 0) { + if ((tselem->flag & TSE_SELECTED) == 0) { outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); - TREESTORE(te)->flag |= TSE_SELECTED; + tselem->flag |= TSE_SELECTED; } /* Gather all selected elements. */ @@ -995,7 +1469,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, WM_drag_add_ID(drag, data.drag_id, data.drag_parent); } - ED_outliner_select_sync_from_all_tag(C); + ED_outliner_select_sync_from_outliner(C, space_outliner); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); } @@ -1027,5 +1501,6 @@ void outliner_dropboxes(void) WM_dropbox_add(lb, "OUTLINER_OT_parent_clear", parent_clear_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_scene_drop", scene_drop_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL); + WM_dropbox_add(lb, "OUTLINER_OT_datastack_drop", datastack_drop_poll, NULL); WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL); } |