Changeset View
Standalone View
source/blender/editors/space_outliner/outliner_dragdrop.c
| Show All 20 Lines | |||||
| * \ingroup spoutliner | * \ingroup spoutliner | ||||
| */ | */ | ||||
| #include <string.h> | #include <string.h> | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "DNA_collection_types.h" | #include "DNA_collection_types.h" | ||||
| #include "DNA_constraint_types.h" | |||||
| #include "DNA_material_types.h" | #include "DNA_material_types.h" | ||||
| #include "DNA_modifier_types.h" | |||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_space_types.h" | #include "DNA_space_types.h" | ||||
| #include "BLI_listbase.h" | #include "BLI_listbase.h" | ||||
| #include "BLI_string.h" | #include "BLI_string.h" | ||||
| #include "BLT_translation.h" | #include "BLT_translation.h" | ||||
| #include "BKE_collection.h" | #include "BKE_collection.h" | ||||
| #include "BKE_constraint.h" | |||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_layer.h" | #include "BKE_layer.h" | ||||
| #include "BKE_lib_id.h" | #include "BKE_lib_id.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_material.h" | #include "BKE_material.h" | ||||
| #include "BKE_object.h" | #include "BKE_object.h" | ||||
| #include "BKE_report.h" | #include "BKE_report.h" | ||||
| #include "BKE_scene.h" | #include "BKE_scene.h" | ||||
| #include "BKE_shader_fx.h" | |||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_build.h" | #include "DEG_depsgraph_build.h" | ||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "ED_outliner.h" | #include "ED_outliner.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| #include "UI_view2d.h" | #include "UI_view2d.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "RNA_enum_types.h" | #include "RNA_enum_types.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "outliner_intern.h" | #include "outliner_intern.h" | ||||
| static Collection *collection_parent_from_ID(ID *id); | |||||
| /* ******************** Drop Data Functions *********************** */ | |||||
| typedef struct OutlinerDropData { | |||||
| Object *ob_parent; | |||||
| bPoseChannel *bone_parent; | |||||
| TreeStoreElem *drag_tselem; | |||||
| void *drag_directdata; | |||||
| int drag_index; | |||||
| int drop_action; | |||||
| TreeElement *drop_te; | |||||
| TreeElementInsertType insert_type; | |||||
| } OutlinerDropData; | |||||
| /* */ | |||||
| static void outliner_drop_data_init(wmDrag *drag, | |||||
| Object *ob, | |||||
| bPoseChannel *pchan, | |||||
| TreeElement *te, | |||||
| TreeStoreElem *tselem, | |||||
| void *directdata) | |||||
| { | |||||
| OutlinerDropData *drop_data = MEM_callocN(sizeof(*drop_data), "outliner drop data"); | |||||
Severin: Minor suggestion: I'd suggest using `sizeof(*drop_data)` here, which means the type will always… | |||||
| drop_data->ob_parent = ob; | |||||
| drop_data->bone_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; | |||||
| } | |||||
| /* ******************** Drop Target Find *********************** */ | /* ******************** Drop Target Find *********************** */ | ||||
| static TreeElement *outliner_dropzone_element(TreeElement *te, | static TreeElement *outliner_dropzone_element(TreeElement *te, | ||||
| const float fmval[2], | const float fmval[2], | ||||
| const bool children) | const bool children) | ||||
| { | { | ||||
| if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) { | if ((fmval[1] > te->ys) && (fmval[1] < (te->ys + UI_UNIT_Y))) { | ||||
| /* name and first icon */ | /* name and first icon */ | ||||
| ▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | static TreeElement *outliner_drop_insert_find(bContext *C, | ||||
| te_hovered = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]); | te_hovered = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]); | ||||
| if (te_hovered) { | if (te_hovered) { | ||||
| /* Mouse hovers an element (ignoring x-axis), | /* Mouse hovers an element (ignoring x-axis), | ||||
| * now find out how to insert the dragged item exactly. */ | * now find out how to insert the dragged item exactly. */ | ||||
| const float margin = UI_UNIT_Y * (1.0f / 4); | const float margin = UI_UNIT_Y * (1.0f / 4); | ||||
| if (view_mval[1] < (te_hovered->ys + margin)) { | 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 */ | /* inserting after a open item means we insert into it, but as first child */ | ||||
| if (BLI_listbase_is_empty(&te_hovered->subtree)) { | if (BLI_listbase_is_empty(&te_hovered->subtree)) { | ||||
| *r_insert_type = TE_INSERT_INTO; | *r_insert_type = TE_INSERT_INTO; | ||||
| return te_hovered; | return te_hovered; | ||||
| } | } | ||||
| *r_insert_type = TE_INSERT_BEFORE; | *r_insert_type = TE_INSERT_BEFORE; | ||||
| return te_hovered->subtree.first; | return te_hovered->subtree.first; | ||||
| } | } | ||||
| Show All 21 Lines | if (view_mval[1] > (first->ys + UI_UNIT_Y)) { | ||||
| *r_insert_type = TE_INSERT_BEFORE; | *r_insert_type = TE_INSERT_BEFORE; | ||||
| return first; | return first; | ||||
| } | } | ||||
| BLI_assert(0); | BLI_assert(0); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te, | static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te, | ||||
| TreeElement **r_te) | TreeElement **r_te) | ||||
Done Inline ActionsCouldn't you just use a non-NULL return value as condition? Don't see a need for both, the bool and the argument return value. Severin: Couldn't you just use a non-`NULL` return value as condition? Don't see a need for both, the… | |||||
| { | { | ||||
| while (te != NULL) { | while (te != NULL) { | ||||
| Collection *collection = outliner_collection_from_tree_element(te); | Collection *collection = outliner_collection_from_tree_element(te); | ||||
| if (collection) { | if (collection) { | ||||
| *r_te = te; | *r_te = te; | ||||
| return collection; | return collection; | ||||
| } | } | ||||
| te = te->parent; | te = te->parent; | ||||
| Show All 23 Lines | static TreeElement *outliner_drop_insert_collection_find(bContext *C, | ||||
| /* We can't insert before/after master collection. */ | /* We can't insert before/after master collection. */ | ||||
| if (collection->flag & COLLECTION_IS_MASTER) { | if (collection->flag & COLLECTION_IS_MASTER) { | ||||
| *r_insert_type = TE_INSERT_INTO; | *r_insert_type = TE_INSERT_INTO; | ||||
| } | } | ||||
| return collection_te; | return collection_te; | ||||
| } | } | ||||
| static Object *outliner_object_from_tree_element_and_parents(TreeElement *te, TreeElement **r_te) | |||||
| { | |||||
| TreeStoreElem *tselem; | |||||
| while (te != NULL) { | |||||
| tselem = TREESTORE(te); | |||||
| if (tselem->type == 0 && te->idcode == ID_OB) { | |||||
| *r_te = te; | |||||
| return (Object *)tselem->id; | |||||
| } | |||||
| te = te->parent; | |||||
| } | |||||
| return NULL; | |||||
Done Inline ActionsThis loop is repeated 3 times, with only minor differences. You could write a utility function for this. Severin: This loop is repeated 3 times, with only minor differences. You could write a utility function… | |||||
| } | |||||
| static bPoseChannel *outliner_bone_from_tree_element_and_parents(TreeElement *te, | |||||
| TreeElement **r_te) | |||||
| { | |||||
| TreeStoreElem *tselem; | |||||
| while (te != NULL) { | |||||
| tselem = TREESTORE(te); | |||||
| if (tselem->type == TSE_POSE_CHANNEL) { | |||||
| *r_te = te; | |||||
| return (bPoseChannel *)te->directdata; | |||||
| } | |||||
| te = te->parent; | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| 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 *********************** */ | /* ******************** Parent Drop Operator *********************** */ | ||||
| static bool parent_drop_allowed(TreeElement *te, Object *potential_child) | static bool parent_drop_allowed(TreeElement *te, Object *potential_child) | ||||
| { | { | ||||
| TreeStoreElem *tselem = TREESTORE(te); | TreeStoreElem *tselem = TREESTORE(te); | ||||
| if (te->idcode != ID_OB || tselem->type != 0) { | if (te->idcode != ID_OB || tselem->type != 0) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 376 Lines • ▼ Show 20 Lines | void OUTLINER_OT_material_drop(wmOperatorType *ot) | ||||
| ot->invoke = material_drop_invoke; | ot->invoke = material_drop_invoke; | ||||
| ot->poll = ED_operator_outliner_active; | ot->poll = ED_operator_outliner_active; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; | ||||
| } | } | ||||
| /* ******************** UI 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. */ | |||||
| enum eUIStackDropAction { | |||||
| UI_STACK_DROP_REORDER, | |||||
| UI_STACK_DROP_COPY, | |||||
| UI_STACK_DROP_LINK, | |||||
| }; | |||||
| static bool uistack_drop_poll(bContext *C, | |||||
| wmDrag *drag, | |||||
| const wmEvent *event, | |||||
Done Inline ActionsI'd call this pchan_parent, otherwise it's confusing/misleading since it's not actually the bone. Severin: I'd call this `pchan_parent`, otherwise it's confusing/misleading since it's not actually the… | |||||
| const char **r_tooltip) | |||||
| { | |||||
| OutlinerDropData *drop_data = drag->poin; | |||||
| if (!drop_data) { | |||||
| return false; | |||||
| } | |||||
| 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; | |||||
| } | |||||
| 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); | |||||
| 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; | |||||
| } | |||||
| TreeElement *object_te; | |||||
Done Inline ActionsThis function does much more than polling, it actually specifies the operation and input data for the dropping. I'd rather avoid that. Could this logic be moved into an own function to determine operation and input data? The poll could then use that to check if the drop is valid, and set the TSE_DRAG_ flag. The drop-invoke could call it again to retrieve the needed data for the actual operation. Note that collection_drop_init() does the same actually. Severin: This function does much more than polling, it actually specifies the operation and input data… | |||||
| TreeElement *bone_te; | |||||
| Object *ob = outliner_object_from_tree_element_and_parents(te_target, &object_te); | |||||
| bPoseChannel *pchan = outliner_bone_from_tree_element_and_parents(te_target, &bone_te); | |||||
| 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 = UI_STACK_DROP_LINK; | |||||
| if (pchan && pchan != drop_data->bone_parent) { | |||||
Done Inline ActionsSame here, would suggest pchan_te? Severin: Same here, would suggest `pchan_te`? | |||||
| *r_tooltip = TIP_("Link all to bone"); | |||||
| drop_data->drop_te = bone_te; | |||||
| tselem_target = TREESTORE(bone_te); | |||||
| } | |||||
| else if (ob && ob != drop_data->ob_parent) { | |||||
| *r_tooltip = TIP_("Link all to object"); | |||||
| 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->bone_parent) { | |||||
| *r_tooltip = TIP_("Copy to bone"); | |||||
| drop_data->insert_type = TE_INSERT_INTO; | |||||
| drop_data->drop_action = UI_STACK_DROP_COPY; | |||||
| drop_data->drop_te = bone_te; | |||||
| tselem_target = TREESTORE(bone_te); | |||||
| } | |||||
| else if (ob && ob != drop_data->ob_parent) { | |||||
| *r_tooltip = TIP_("Copy to object"); | |||||
| drop_data->insert_type = TE_INSERT_INTO; | |||||
| drop_data->drop_action = UI_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; | |||||
| } | |||||
| *r_tooltip = TIP_("Reorder"); | |||||
| drop_data->drop_action = UI_STACK_DROP_REORDER; | |||||
| drop_data->drop_te = te_target; | |||||
| } | |||||
| else { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| else { | |||||
| return false; | |||||
| } | |||||
| 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; | |||||
| } | |||||
| if (changed) { | |||||
| ED_region_tag_redraw_no_rebuild(region); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static void uistack_drop_link(bContext *C, OutlinerDropData *drop_data) | |||||
Done Inline ActionsUse a switch here, so it's obvious on first sight that this logic only handles the different types (there could be other checks in the `else if'-blocks below, you have to read them to see if that's the case). Severin: Use a switch here, so it's obvious on first sight that this logic only handles the different… | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); | |||||
Done Inline ActionsI'd add these function calls to ED_object_link_modifiers(). Same for the ones below. Severin: I'd add these function calls to `ED_object_link_modifiers()`. Same for the ones below. | |||||
| 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->bone_parent) { | |||||
| src = &drop_data->bone_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; | |||||
| } | |||||
| BKE_constraints_copy(dst, src, true); | |||||
| LISTBASE_FOREACH (bConstraint *, con, dst) { | |||||
| ED_object_constraint_dependency_tag_update(bmain, ob_dst, con); | |||||
| } | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT | NA_ADDED, NULL); | |||||
| break; | |||||
| } | |||||
| case TSE_GPENCIL_EFFECT_BASE: | |||||
| if (ob_dst->type != OB_GPENCIL) { | |||||
| return; | |||||
| } | |||||
| BKE_shaderfx_copy(&ob_dst->shader_fx, &drop_data->ob_parent->shader_fx); | |||||
| DEG_id_tag_update(&ob_dst->id, ID_RECALC_GEOMETRY); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_SHADERFX, ob_dst); | |||||
| break; | |||||
| } | |||||
| } | |||||
| static void uistack_drop_copy(bContext *C, OutlinerDropData *drop_data) | |||||
Done Inline ActionsSame here, better use a switch. Severin: Same here, better use a switch. | |||||
| { | |||||
| 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) { | |||||
| BKE_object_link_gpencil_modifier(ob_dst, drop_data->drag_directdata); | |||||
| } | |||||
| else if (drop_data->ob_parent->type != OB_GPENCIL && ob_dst->type != OB_GPENCIL) { | |||||
| BKE_object_link_modifier(ob_dst, drop_data->ob_parent, drop_data->drag_directdata); | |||||
| } | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob_dst); | |||||
Done Inline ActionsWhat I meant with my earlier comment is that I think all these things should be handled in ED_ functions. The Outliner shouldn't know which notifiers have to be sent or which depsgraph tags have to be set if an operation is executed. The ED code should do that. Note that you don't need the context for that, you can call WM_main_add_notifier(). The context is only needed to get the window for specific screen-level handling. Severin: What I meant with my earlier comment is that I think all these things should be handled in… | |||||
| DEG_id_tag_update(&ob_dst->id, | |||||
| ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); | |||||
| break; | |||||
| case TSE_CONSTRAINT: | |||||
| if (tselem->type == TSE_POSE_CHANNEL) { | |||||
| BKE_constraint_copy_for_pose( | |||||
| ob_dst, drop_data->drop_te->directdata, drop_data->drag_directdata); | |||||
| } | |||||
| else { | |||||
| BKE_constraint_copy_for_object(ob_dst, drop_data->drag_directdata); | |||||
| } | |||||
| ED_object_constraint_dependency_tag_update(bmain, ob_dst, drop_data->drag_directdata); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT | NA_ADDED, ob_dst); | |||||
| break; | |||||
Done Inline ActionsWhy is this lower-level logic here? I'd expect there would be functions for this, likely on ED_ level. Severin: Why is this lower-level logic here? I'd expect there would be functions for this, likely on… | |||||
| case TSE_GPENCIL_EFFECT: { | |||||
| if (ob_dst->type != OB_GPENCIL) { | |||||
| return; | |||||
| } | |||||
| ShaderFxData *fx = drop_data->drag_directdata; | |||||
| ShaderFxData *nfx = BKE_shaderfx_new(fx->type); | |||||
| BLI_strncpy(nfx->name, fx->name, sizeof(nfx->name)); | |||||
| BKE_shaderfx_copydata(fx, nfx); | |||||
| BLI_addtail(&ob_dst->shader_fx, nfx); | |||||
| DEG_id_tag_update(&ob_dst->id, ID_RECALC_GEOMETRY); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_SHADERFX, ob_dst); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| static void uistack_drop_reorder(bContext *C, ReportList *reports, OutlinerDropData *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; | |||||
| TreeStoreElem *tselem = TREESTORE(drop_data->drop_te); | |||||
| TreeElementInsertType insert_type = drop_data->insert_type; | |||||
| Object *ob_dst = (Object *)tselem->id; | |||||
| Object *ob = drop_data->ob_parent; | |||||
| int index = 0; | |||||
| switch (drop_data->drag_tselem->type) { | |||||
| case TSE_MODIFIER: | |||||
| if (ob->type == OB_GPENCIL && ob_dst->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 && ob_dst->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); | |||||
| } | |||||
| DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob); | |||||
| break; | |||||
| case TSE_CONSTRAINT: | |||||
| if (drop_data->bone_parent) { | |||||
| index = outliner_get_insert_index( | |||||
| drag_te, drop_te, insert_type, &drop_data->bone_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); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob); | |||||
| 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); | |||||
| DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_SHADERFX, ob); | |||||
| } | |||||
| } | |||||
| static int uistack_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; | |||||
| OutlinerDropData *drop_data = drag->poin; | |||||
| switch (drop_data->drop_action) { | |||||
| case UI_STACK_DROP_LINK: | |||||
| uistack_drop_link(C, drop_data); | |||||
| break; | |||||
| case UI_STACK_DROP_COPY: | |||||
| uistack_drop_copy(C, drop_data); | |||||
Done Inline ActionsI find UI stack a bit misleading, since were dealing with scene data, not UI. Maybe call it data-stack then? Not too discriptive either, but at least not misleading I think. Severin: I find UI stack a bit misleading, since were dealing with scene data, not UI. Maybe call it… | |||||
| break; | |||||
| case UI_STACK_DROP_REORDER: | |||||
| uistack_drop_reorder(C, op->reports, drop_data); | |||||
| break; | |||||
| } | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_uistack_drop(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "UI Stack Drop"; | |||||
| ot->description = "Copy or reorder modifiers, constraints, and effects"; | |||||
| ot->idname = "OUTLINER_OT_uistack_drop"; | |||||
| /* api callbacks */ | |||||
| ot->invoke = uistack_drop_invoke; | |||||
| ot->poll = ED_operator_outliner_active; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; | |||||
| } | |||||
| /* ******************** Collection Drop Operator *********************** */ | /* ******************** Collection Drop Operator *********************** */ | ||||
| typedef struct CollectionDrop { | typedef struct CollectionDrop { | ||||
| Collection *from; | Collection *from; | ||||
| Collection *to; | Collection *to; | ||||
| TreeElement *te; | TreeElement *te; | ||||
| TreeElementInsertType insert_type; | TreeElementInsertType insert_type; | ||||
| ▲ Show 20 Lines • Show All 250 Lines • ▼ Show 20 Lines | static int outliner_item_drag_drop_invoke(bContext *C, | ||||
| ARegion *region = CTX_wm_region(C); | ARegion *region = CTX_wm_region(C); | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | ||||
| TreeElement *te = outliner_item_drag_element_find(space_outliner, region, event); | TreeElement *te = outliner_item_drag_element_find(space_outliner, region, event); | ||||
| if (!te) { | if (!te) { | ||||
| return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | 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) { | if (!data.drag_id) { | ||||
| return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | ||||
| } | } | ||||
| float view_mval[2]; | float view_mval[2]; | ||||
| UI_view2d_region_to_view( | UI_view2d_region_to_view( | ||||
| ®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); | ®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); | ||||
| if (outliner_item_is_co_within_close_toggle(te, view_mval[0])) { | if (outliner_item_is_co_within_close_toggle(te, view_mval[0])) { | ||||
| return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); | ||||
| } | } | ||||
| /* Scroll the view when dragging near edges, but not | /* Scroll the view when dragging near edges, but not | ||||
| * when the drag goes too far outside the region. */ | * when the drag goes too far outside the region. */ | ||||
| { | { | ||||
| wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); | wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); | ||||
| PointerRNA op_ptr; | PointerRNA op_ptr; | ||||
| WM_operator_properties_create_ptr(&op_ptr, ot); | WM_operator_properties_create_ptr(&op_ptr, ot); | ||||
| RNA_int_set(&op_ptr, "outside_padding", OUTLINER_DRAG_SCOLL_OUTSIDE_PAD); | RNA_int_set(&op_ptr, "outside_padding", OUTLINER_DRAG_SCOLL_OUTSIDE_PAD); | ||||
| WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_ptr); | WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_ptr); | ||||
| WM_operator_properties_free(&op_ptr); | WM_operator_properties_free(&op_ptr); | ||||
| } | } | ||||
| wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP); | wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP); | ||||
| if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { | if (ELEM(tselem->type, | ||||
| TSE_MODIFIER, | |||||
| TSE_MODIFIER_BASE, | |||||
| TSE_CONSTRAINT, | |||||
| TSE_CONSTRAINT_BASE, | |||||
| TSE_GPENCIL_EFFECT, | |||||
| TSE_GPENCIL_EFFECT_BASE)) { | |||||
| TreeElement *te_bone = NULL; | |||||
| bPoseChannel *pchan = outliner_find_parent_bone(te, &te_bone); | |||||
| outliner_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. */ | /* For collections and objects we cheat and drag all selected. */ | ||||
| /* Only drag element under mouse if it was not selected before. */ | /* 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); | outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); | ||||
| TREESTORE(te)->flag |= TSE_SELECTED; | tselem->flag |= TSE_SELECTED; | ||||
| } | } | ||||
| /* Gather all selected elements. */ | /* Gather all selected elements. */ | ||||
| struct IDsSelectedData selected = { | struct IDsSelectedData selected = { | ||||
| .selected_array = {NULL, NULL}, | .selected_array = {NULL, NULL}, | ||||
| }; | }; | ||||
| if (GS(data.drag_id->name) == ID_OB) { | if (GS(data.drag_id->name) == ID_OB) { | ||||
| ▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | else if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) { | ||||
| BLI_freelistN(&selected.selected_array); | BLI_freelistN(&selected.selected_array); | ||||
| } | } | ||||
| else { | else { | ||||
| /* Add single ID. */ | /* Add single ID. */ | ||||
| WM_drag_add_ID(drag, data.drag_id, data.drag_parent); | 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); | return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); | ||||
| } | } | ||||
| /* Outliner drag and drop. This operator mostly exists to support dragging | /* Outliner drag and drop. This operator mostly exists to support dragging | ||||
| * from outliner text instead of only from the icon, and also to show a | * from outliner text instead of only from the icon, and also to show a | ||||
| * hint in the statusbar keymap. */ | * hint in the statusbar keymap. */ | ||||
| Show All 15 Lines | |||||
| void outliner_dropboxes(void) | void outliner_dropboxes(void) | ||||
| { | { | ||||
| ListBase *lb = WM_dropboxmap_find("Outliner", SPACE_OUTLINER, RGN_TYPE_WINDOW); | ListBase *lb = WM_dropboxmap_find("Outliner", SPACE_OUTLINER, RGN_TYPE_WINDOW); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_parent_drop", parent_drop_poll, NULL); | WM_dropbox_add(lb, "OUTLINER_OT_parent_drop", parent_drop_poll, NULL); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_parent_clear", parent_clear_poll, NULL); | 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_scene_drop", scene_drop_poll, NULL); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL); | WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL); | ||||
| WM_dropbox_add(lb, "OUTLINER_OT_uistack_drop", uistack_drop_poll, NULL); | |||||
| WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL); | WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL); | ||||
| } | } | ||||
Minor suggestion: I'd suggest using sizeof(*drop_data) here, which means the type will always follows the one of the variable.