Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_outliner/outliner_dragdrop.c
| Show First 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | |||||
| #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 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 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | static TreeElement *outliner_drop_insert_collection_find(bContext *C, | ||||
| } | } | ||||
| TreeElement *collection_te; | TreeElement *collection_te; | ||||
| Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te); | Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te); | ||||
| if (!collection) { | if (!collection) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); | |||||
| if (space_outliner->sort_method != SO_SORT_FREE) { | |||||
| *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; | ||||
| } | } | ||||
| return collection_te; | return collection_te; | ||||
| } | } | ||||
| /* ******************** Parent Drop Operator *********************** */ | /* ******************** Parent Drop Operator *********************** */ | ||||
| static bool parent_drop_allowed(TreeElement *te, Object *potential_child) | static bool parent_drop_allowed(bContext *C, | ||||
| const wmEvent *event, | |||||
| TreeElement *te, | |||||
| Object *potential_child) | |||||
| { | { | ||||
| 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]); | |||||
| /* Check if over name. */ | |||||
| if ((view_mval[0] < te->xs + UI_UNIT_X) || (view_mval[0] > te->xend)) { | |||||
Severin: Always a good idea to encapsulate such low level checks into a function, ie see… | |||||
| 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_FREE || 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); | |||||
| } | |||||
| Object *ob_drop = (Object *)TREESTORE(te)->id; | |||||
SeverinUnsubmitted Done Inline ActionsBetter assert again that the ID is indeed an object. Severin: Better assert again that the ID is indeed an object. | |||||
| Collection *collection_to = collection_parent_from_ID(&ob_drop->id); | |||||
| while (te) { | |||||
| te = te->parent; | |||||
| if (outliner_is_collection_tree_element(te)) { | |||||
SeverinUnsubmitted 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… | |||||
| 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_FREE || space_outliner->outlinevis != SO_VIEW_LAYER) { | |||||
SeverinUnsubmitted 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… | |||||
| 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 288 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 120 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])) { | ||||
| ▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Lines | 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 25 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 ;)