Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_outliner/outliner_dragdrop.c
| Show First 20 Lines • Show All 231 Lines • ▼ Show 20 Lines | static TreeElement *outliner_drop_insert_collection_find(bContext *C, | ||||
| TreeElement *collection_te = outliner_data_from_tree_element_and_parents(is_collection_element, | TreeElement *collection_te = outliner_data_from_tree_element_and_parents(is_collection_element, | ||||
| te); | te); | ||||
| if (!collection_te) { | if (!collection_te) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| Collection *collection = outliner_collection_from_tree_element(collection_te); | Collection *collection = outliner_collection_from_tree_element(collection_te); | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | |||||
| if (space_outliner->sort_method != SO_SORT_CUSTOM) { | |||||
| *r_insert_type = TE_INSERT_INTO; | |||||
| } | |||||
| if (collection_te != te) { | if (collection_te != te) { | ||||
| *r_insert_type = TE_INSERT_INTO; | *r_insert_type = TE_INSERT_INTO; | ||||
| } | } | ||||
| /* 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; | ||||
| } | } | ||||
| Show All 22 Lines | if (drop_te == NULL) { | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| return BLI_findindex(listbase, drop_te->directdata); | 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 outliner_item_is_co_over_name(const TreeElement *te, const float view_co_x) | ||||
| { | { | ||||
| return (view_co_x < te->xs + UI_UNIT_X) || (view_co_x > te->xend); | |||||
| } | |||||
| static bool parent_drop_allowed(bContext *C, | |||||
| const wmEvent *event, | |||||
| TreeElement *te, | |||||
| Object *potential_child) | |||||
| { | |||||
Severin: Always a good idea to encapsulate such low level checks into a function, ie see… | |||||
| ARegion *region = CTX_wm_region(C); | |||||
| float view_mval[2]; | |||||
| UI_view2d_region_to_view( | |||||
| ®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); | |||||
| if (outliner_item_is_co_over_name(te, view_mval[0])) { | |||||
| return false; | |||||
| } | |||||
| 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; | ||||
| } | } | ||||
| Object *potential_parent = (Object *)tselem->id; | Object *potential_parent = (Object *)tselem->id; | ||||
| if (potential_parent == potential_child) { | if (potential_parent == potential_child) { | ||||
| Show All 18 Lines | LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| static bool allow_parenting_without_modifier_key(SpaceOutliner *space_outliner) | |||||
| { | |||||
| switch (space_outliner->outlinevis) { | |||||
| case SO_VIEW_LAYER: | |||||
| return space_outliner->filter & SO_FILTER_NO_COLLECTION; | |||||
| case SO_SCENES: | |||||
| return true; | |||||
| default: | |||||
| return false; | |||||
| } | |||||
| } | |||||
| static bool parent_drop_poll(bContext *C, | static bool parent_drop_poll(bContext *C, | ||||
| wmDrag *drag, | wmDrag *drag, | ||||
| const wmEvent *event, | const wmEvent *event, | ||||
| const char **UNUSED(r_tooltip)) | const char **r_tooltip) | ||||
| { | { | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | ||||
| bool changed = outliner_flag_set(&space_outliner->tree, TSE_DRAG_ANY, false); | bool changed = outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false); | ||||
| if (changed) { | if (changed) { | ||||
| ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); | ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); | ||||
| } | } | ||||
| Object *potential_child = (Object *)WM_drag_ID(drag, ID_OB); | Object *potential_child = (Object *)WM_drag_ID(drag, ID_OB); | ||||
| if (!potential_child) { | if (!potential_child) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (!allow_parenting_without_modifier_key(space_outliner)) { | TreeElementInsertType insert_type; | ||||
| if (!event->shift) { | TreeElement *te = outliner_drop_insert_find(C, event, &insert_type); | ||||
| if (!te) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| TreeStoreElem *tselem = TREESTORE(te); | |||||
| if (space_outliner->sort_method != SO_SORT_CUSTOM || | |||||
| space_outliner->outlinevis != SO_VIEW_LAYER) { | |||||
| insert_type = TE_INSERT_INTO; | |||||
| } | } | ||||
| TreeElement *te = outliner_drop_find(C, event); | if (!parent_drop_allowed(C, event, te, potential_child)) { | ||||
| if (!te) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| if (parent_drop_allowed(te, potential_child)) { | switch (insert_type) { | ||||
| case TE_INSERT_BEFORE: | |||||
| tselem->flag |= TSE_DRAG_BEFORE; | |||||
| *r_tooltip = TIP_("Reorder object"); | |||||
| break; | |||||
| case TE_INSERT_AFTER: | |||||
| tselem->flag |= TSE_DRAG_AFTER; | |||||
| *r_tooltip = TIP_("Reorder object"); | |||||
| break; | |||||
| case TE_INSERT_INTO: | |||||
| tselem->flag |= TSE_DRAG_INTO; | |||||
| break; | |||||
| } | |||||
| TREESTORE(te)->flag |= TSE_DRAG_INTO; | TREESTORE(te)->flag |= TSE_DRAG_INTO; | ||||
| ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); | ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); | ||||
| return true; | |||||
| } | |||||
| return false; | return true; | ||||
| } | } | ||||
| static void parent_drop_set_parents(bContext *C, | static void parent_drop_set_parents(bContext *C, | ||||
| ReportList *reports, | ReportList *reports, | ||||
| wmDragID *drag, | wmDragID *drag, | ||||
| Object *parent, | Object *parent, | ||||
| short parent_type, | short parent_type, | ||||
| const bool keep_transform) | const bool keep_transform) | ||||
| Show All 39 Lines | static void parent_drop_set_parents(bContext *C, | ||||
| if (parent_set) { | if (parent_set) { | ||||
| DEG_relations_tag_update(bmain); | DEG_relations_tag_update(bmain); | ||||
| WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); | WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); | ||||
| WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL); | WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL); | ||||
| } | } | ||||
| } | } | ||||
| static void parent_drop_move_objects(bContext *C, wmDragID *drag, TreeElement *te) | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = (Scene *)outliner_search_back(te, ID_SCE); | |||||
| if (scene == NULL) { | |||||
| scene = CTX_data_scene(C); | |||||
| } | |||||
| TreeStoreElem *tselem = TREESTORE(te); | |||||
Done Inline ActionsBetter assert again that the ID is indeed an object. Severin: Better assert again that the ID is indeed an object. | |||||
| BLI_assert(tselem->type == 0 && te->idcode == ID_OB); | |||||
| Object *ob_drop = (Object *)TREESTORE(te)->id; | |||||
| Collection *collection_to = collection_parent_from_ID(&ob_drop->id); | |||||
Not Done Inline ActionsI have seen these kind of parent lookups before, haven't I ;) One more case where I think a parent lookup function taking a callback to test the criteria should be used. Severin: I have seen these kind of parent lookups before, haven't I ;) One more case where I think a… | |||||
| while (te) { | |||||
| te = te->parent; | |||||
| if (outliner_is_collection_tree_element(te)) { | |||||
| collection_to = outliner_collection_from_tree_element(te); | |||||
| break; | |||||
| } | |||||
| } | |||||
| for (wmDragID *drag_id = drag; drag_id; drag_id = drag_id->next) { | |||||
| if (GS(drag_id->id->name) == ID_OB) { | |||||
| Object *object = (Object *)drag_id->id; | |||||
| /* Do nothing to linked data */ | |||||
| if (ID_IS_LINKED(object)) { | |||||
| continue; | |||||
| } | |||||
| Collection *from = collection_parent_from_ID(drag_id->from_parent); | |||||
| BKE_collection_object_move(bmain, scene, collection_to, from, object); | |||||
| BKE_collection_object_move_after(bmain, collection_to, ob_drop, object); | |||||
| } | |||||
| } | |||||
| DEG_relations_tag_update(bmain); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL); | |||||
| WM_event_add_notifier(C, NC_OBJECT | ND_PARENT, NULL); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_LAYER, NULL); | |||||
| } | |||||
| static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event) | static int parent_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event) | ||||
| { | { | ||||
| TreeElement *te = outliner_drop_find(C, event); | TreeElementInsertType insert_type; | ||||
| TreeElement *te = outliner_drop_insert_find(C, event, &insert_type); | |||||
| TreeStoreElem *tselem = te ? TREESTORE(te) : NULL; | TreeStoreElem *tselem = te ? TREESTORE(te) : NULL; | ||||
| if (!(te && te->idcode == ID_OB && tselem->type == 0)) { | if (!(te && te->idcode == ID_OB && tselem->type == 0)) { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| Object *par = (Object *)tselem->id; | Object *par = (Object *)tselem->id; | ||||
| Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB); | Object *ob = (Object *)WM_drag_ID_from_event(event, ID_OB); | ||||
| if (ELEM(NULL, ob, par)) { | if (ELEM(NULL, ob, par)) { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| if (ob == par) { | if (ob == par) { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| if (event->custom != EVT_DATA_DRAGDROP) { | if (event->custom != EVT_DATA_DRAGDROP) { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| ListBase *lb = event->customdata; | ListBase *lb = event->customdata; | ||||
| wmDrag *drag = lb->first; | wmDrag *drag = lb->first; | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | |||||
| if (space_outliner->sort_method != SO_SORT_CUSTOM || | |||||
Not Done Inline ActionsI could imagine we'd want to support sorting objects in more display modes. So maybe put this into a function, say bool outliner_allows_custom_order(const SpaceOutliner *). Severin: I could imagine we'd want to support sorting objects in more display modes. So maybe put this… | |||||
| space_outliner->outlinevis != SO_VIEW_LAYER) { | |||||
| insert_type = TE_INSERT_INTO; | |||||
| } | |||||
| if (insert_type == TE_INSERT_INTO) { | |||||
| parent_drop_set_parents(C, op->reports, drag->ids.first, par, PAR_OBJECT, event->alt); | parent_drop_set_parents(C, op->reports, drag->ids.first, par, PAR_OBJECT, event->alt); | ||||
| } | |||||
| else { | |||||
| parent_drop_move_objects(C, drag->ids.first, te); | |||||
| } | |||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| void OUTLINER_OT_parent_drop(wmOperatorType *ot) | void OUTLINER_OT_parent_drop(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Drop to Set Parent [+Alt keeps transforms]"; | ot->name = "Drop to Set Parent [+Alt keeps transforms]"; | ||||
| Show All 11 Lines | |||||
| /* ******************** Parent Clear Operator *********************** */ | /* ******************** Parent Clear Operator *********************** */ | ||||
| static bool parent_clear_poll(bContext *C, | static bool parent_clear_poll(bContext *C, | ||||
| wmDrag *drag, | wmDrag *drag, | ||||
| const wmEvent *event, | const wmEvent *event, | ||||
| const char **UNUSED(r_tooltip)) | const char **UNUSED(r_tooltip)) | ||||
| { | { | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | |||||
| if (!allow_parenting_without_modifier_key(space_outliner)) { | |||||
| if (!event->shift) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| Object *ob = (Object *)WM_drag_ID(drag, ID_OB); | Object *ob = (Object *)WM_drag_ID(drag, ID_OB); | ||||
| if (!ob) { | if (!ob) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (!ob->parent) { | if (!ob->parent) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 695 Lines • ▼ Show 20 Lines | if (!data.from || event->ctrl) { | ||||
| changed = true; | changed = true; | ||||
| *r_tooltip = TIP_("Link inside Collection"); | *r_tooltip = TIP_("Link inside Collection"); | ||||
| } | } | ||||
| else { | else { | ||||
| switch (data.insert_type) { | switch (data.insert_type) { | ||||
| case TE_INSERT_BEFORE: | case TE_INSERT_BEFORE: | ||||
| tselem->flag |= TSE_DRAG_BEFORE; | tselem->flag |= TSE_DRAG_BEFORE; | ||||
| changed = true; | changed = true; | ||||
| if (te->prev && outliner_is_collection_tree_element(te->prev)) { | *r_tooltip = TIP_("Reorder collection(s)"); | ||||
| *r_tooltip = TIP_("Move between collections"); | |||||
| } | |||||
| else { | |||||
| *r_tooltip = TIP_("Move before collection"); | |||||
| } | |||||
| break; | break; | ||||
| case TE_INSERT_AFTER: | case TE_INSERT_AFTER: | ||||
| tselem->flag |= TSE_DRAG_AFTER; | tselem->flag |= TSE_DRAG_AFTER; | ||||
| changed = true; | changed = true; | ||||
| if (te->next && outliner_is_collection_tree_element(te->next)) { | *r_tooltip = TIP_("Reorder collection(s)"); | ||||
| *r_tooltip = TIP_("Move between collections"); | |||||
| } | |||||
| else { | |||||
| *r_tooltip = TIP_("Move after collection"); | |||||
| } | |||||
| break; | break; | ||||
| case TE_INSERT_INTO: | case TE_INSERT_INTO: | ||||
| tselem->flag |= TSE_DRAG_INTO; | tselem->flag |= TSE_DRAG_INTO; | ||||
| changed = true; | changed = true; | ||||
| *r_tooltip = TIP_("Move inside collection (Ctrl to link, Shift to parent)"); | *r_tooltip = TIP_("Move inside collection (Ctrl to link, Shift to parent)"); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 289 Lines • Show Last 20 Lines | |||||
Always a good idea to encapsulate such low level checks into a function, ie see outliner_item_is_co_over_name_icons(). Then you also don't need a comment to explain what this does ;)