Support drag & drop of collections across multiple hierarchy levels
Two issues are remaining, they'll be fixed separately: * Graphical feedback when dragging within the master collection is wrong * There's some bug where collections swap places instead, Dalai will investigate
This commit is contained in:
@@ -1520,34 +1520,14 @@ static void outliner_draw_tree_element(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count how many visible childs (and open grandchilds, great-grandchilds, ...) \a te has.
|
||||
*/
|
||||
static int outliner_count_visible_childs(const SpaceOops *soops, const TreeElement *te)
|
||||
{
|
||||
TreeStoreElem *tselem = TREESTORE(te);
|
||||
int current_count = 0;
|
||||
|
||||
if (TSELEM_OPEN(tselem, soops)) {
|
||||
for (TreeElement *te_child = te->subtree.first; te_child; te_child = te_child->next) {
|
||||
current_count += outliner_count_visible_childs(soops, te_child);
|
||||
current_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return current_count;
|
||||
}
|
||||
|
||||
static void outliner_draw_tree_element_floating(const SpaceOops *soops, const ARegion *ar,
|
||||
const TreeElement *te_floating)
|
||||
static void outliner_draw_tree_element_floating(
|
||||
const ARegion *ar, const TreeElement *te_floating)
|
||||
{
|
||||
const TreeElement *te_insert = te_floating->drag_data->insert_handle;
|
||||
const ListBase *lb_parent = te_floating->parent ? &te_floating->parent->subtree : &soops->tree;
|
||||
const TreeElement *te_insert_fallback = te_insert ? te_insert : lb_parent->first;
|
||||
const int line_width = 2;
|
||||
|
||||
unsigned int pos = add_attrib(immVertexFormat(), "pos", GL_FLOAT, 2, KEEP_FLOAT);
|
||||
int coord_y = (te_insert ? te_insert->ys : (te_insert_fallback->ys + UI_UNIT_Y)) - (int)(line_width * 0.5f);
|
||||
int coord_y = te_insert->ys;
|
||||
unsigned char col[4];
|
||||
|
||||
if (te_insert == te_floating) {
|
||||
@@ -1555,15 +1535,14 @@ static void outliner_draw_tree_element_floating(const SpaceOops *soops, const AR
|
||||
return;
|
||||
}
|
||||
|
||||
if (te_insert) {
|
||||
coord_y -= UI_UNIT_Y * outliner_count_visible_childs(soops, te_insert);
|
||||
}
|
||||
|
||||
UI_GetThemeColorShade4ubv(TH_BACK, -40, col);
|
||||
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
if (!te_insert || (te_floating->drag_data->insert_type == TE_INSERT_AFTER)) {
|
||||
if (ELEM(te_floating->drag_data->insert_type, TE_INSERT_BEFORE, TE_INSERT_AFTER)) {
|
||||
if (te_floating->drag_data->insert_type == TE_INSERT_BEFORE) {
|
||||
coord_y += UI_UNIT_Y;
|
||||
}
|
||||
immUniformColor4ubv(col);
|
||||
glLineWidth(line_width);
|
||||
|
||||
@@ -1793,7 +1772,7 @@ static void outliner_draw_tree(
|
||||
startx, &starty, te_edit, &te_floating);
|
||||
}
|
||||
if (te_floating) {
|
||||
outliner_draw_tree_element_floating(soops, ar, te_floating);
|
||||
outliner_draw_tree_element_floating(ar, te_floating);
|
||||
}
|
||||
|
||||
if (has_restrict_icons) {
|
||||
|
||||
@@ -50,7 +50,7 @@ struct wmKeyConfig;
|
||||
|
||||
|
||||
typedef enum TreeElementInsertType {
|
||||
/* no INSERT_BEFORE needed for now */
|
||||
TE_INSERT_BEFORE,
|
||||
TE_INSERT_AFTER,
|
||||
TE_INSERT_INTO,
|
||||
} TreeElementInsertType;
|
||||
@@ -91,7 +91,7 @@ typedef struct TreeElement {
|
||||
|
||||
struct {
|
||||
TreeElementInsertType insert_type;
|
||||
/* the element after which we may insert the dragged one (NULL to insert at top) */
|
||||
/* the element before/after/into which we may insert the dragged one (NULL to insert at top) */
|
||||
struct TreeElement *insert_handle;
|
||||
} *drag_data;
|
||||
} TreeElement;
|
||||
|
||||
@@ -52,11 +52,6 @@ enum {
|
||||
OUTLINER_ITEM_DRAG_CONFIRM,
|
||||
};
|
||||
|
||||
typedef struct OutlinerItemDrag {
|
||||
TreeElement *dragged_te;
|
||||
int init_mouse_xy[2];
|
||||
} OutlinerItemDrag;
|
||||
|
||||
static int outliner_item_drag_drop_poll(bContext *C)
|
||||
{
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
@@ -67,70 +62,69 @@ static int outliner_item_drag_drop_poll(bContext *C)
|
||||
|
||||
static TreeElement *outliner_item_drag_element_find(SpaceOops *soops, ARegion *ar, const wmEvent *event)
|
||||
{
|
||||
/* note: using EVT_TWEAK_ events to trigger dragging is fine,
|
||||
* it sends coordinates from where dragging was started */
|
||||
const float my = UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]);
|
||||
return outliner_find_item_at_y(soops, &soops->tree, my);
|
||||
}
|
||||
|
||||
static OutlinerItemDrag *outliner_item_drag_data_create(TreeElement *dragged_te, const int mouse_xy[2])
|
||||
static void outliner_item_drag_end(TreeElement *dragged_te)
|
||||
{
|
||||
OutlinerItemDrag *drag_data = MEM_mallocN(sizeof(*drag_data), __func__);
|
||||
|
||||
drag_data->dragged_te = dragged_te;
|
||||
copy_v2_v2_int(drag_data->init_mouse_xy, mouse_xy);
|
||||
|
||||
return drag_data;
|
||||
MEM_SAFE_FREE(dragged_te->drag_data);
|
||||
}
|
||||
|
||||
static void outliner_item_drag_end(OutlinerItemDrag *op_drag_data)
|
||||
static void outliner_item_drag_handle(
|
||||
SpaceOops *soops, ARegion *ar, const wmEvent *event, TreeElement *te_dragged)
|
||||
{
|
||||
MEM_SAFE_FREE(op_drag_data->dragged_te->drag_data);
|
||||
MEM_freeN(op_drag_data);
|
||||
}
|
||||
TreeStoreElem *tselem_dragged = TREESTORE(te_dragged);
|
||||
TreeElement *insert_handle;
|
||||
float view_mval[2];
|
||||
|
||||
static void outliner_item_drag_handle(ARegion *ar, const wmEvent *event, OutlinerItemDrag *op_drag_data)
|
||||
{
|
||||
TreeElement *dragged_te = op_drag_data->dragged_te;
|
||||
const int delta_mouse_y = event->y - op_drag_data->init_mouse_xy[1];
|
||||
const int cmp_coord = (int)UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]);
|
||||
const float margin = UI_UNIT_Y * (1.0f / 3);
|
||||
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]);
|
||||
insert_handle = outliner_find_item_at_y(soops, &soops->tree, view_mval[1]);
|
||||
|
||||
/* by default we don't change the item position */
|
||||
dragged_te->drag_data->insert_handle = dragged_te;
|
||||
te_dragged->drag_data->insert_handle = NULL;
|
||||
if (insert_handle) {
|
||||
TreeStoreElem *tselem_handle = TREESTORE(insert_handle);
|
||||
if (tselem_handle->type == tselem_dragged->type) {
|
||||
const float margin = UI_UNIT_Y * (1.0f / 4);
|
||||
|
||||
if (delta_mouse_y > 0) {
|
||||
for (TreeElement *te = dragged_te->prev; te && (cmp_coord >= (te->ys + margin)); te = te->prev) {
|
||||
if (cmp_coord > (te->ys + (2 * margin))) {
|
||||
dragged_te->drag_data->insert_type = TE_INSERT_AFTER;
|
||||
/* will be NULL if we want to insert as first element */
|
||||
dragged_te->drag_data->insert_handle = te->prev;
|
||||
te_dragged->drag_data->insert_handle = insert_handle;
|
||||
if (view_mval[1] < (insert_handle->ys + margin)) {
|
||||
te_dragged->drag_data->insert_type = TE_INSERT_AFTER;
|
||||
}
|
||||
else if (view_mval[1] > (insert_handle->ys + (2 * margin))) {
|
||||
te_dragged->drag_data->insert_type = TE_INSERT_BEFORE;
|
||||
}
|
||||
else {
|
||||
dragged_te->drag_data->insert_type = TE_INSERT_INTO;
|
||||
dragged_te->drag_data->insert_handle = te;
|
||||
te_dragged->drag_data->insert_type = TE_INSERT_INTO;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (TreeElement *te = dragged_te->next; te && (cmp_coord <= (te->ys + UI_UNIT_Y - margin)); te = te->next) {
|
||||
if (cmp_coord < (te->ys + margin)) {
|
||||
dragged_te->drag_data->insert_type = TE_INSERT_AFTER;
|
||||
dragged_te->drag_data->insert_handle = te;
|
||||
BLI_assert(te->prev != NULL);
|
||||
}
|
||||
else {
|
||||
dragged_te->drag_data->insert_type = TE_INSERT_INTO;
|
||||
dragged_te->drag_data->insert_handle = te;
|
||||
}
|
||||
TreeElement *first = soops->tree.first;
|
||||
TreeElement *last = soops->tree.last;
|
||||
|
||||
/* mouse doesn't hover any item (ignoring x axis), so it's either above list bounds or below. */
|
||||
if (view_mval[1] < last->ys) {
|
||||
te_dragged->drag_data->insert_handle = last;
|
||||
te_dragged->drag_data->insert_type = TE_INSERT_AFTER;
|
||||
}
|
||||
else if (view_mval[1] > (first->ys + UI_UNIT_Y)) {
|
||||
te_dragged->drag_data->insert_handle = first;
|
||||
te_dragged->drag_data->insert_type = TE_INSERT_BEFORE;
|
||||
}
|
||||
else {
|
||||
BLI_assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool outliner_item_drag_drop_apply(const Scene *scene, OutlinerItemDrag *op_drag_data)
|
||||
static bool outliner_item_drag_drop_apply(const Scene *scene, TreeElement *dragged_te)
|
||||
{
|
||||
TreeElement *dragged_te = op_drag_data->dragged_te;
|
||||
TreeElement *insert_after = dragged_te->drag_data->insert_handle;
|
||||
TreeElement *insert_handle = dragged_te->drag_data->insert_handle;
|
||||
|
||||
if (insert_after == dragged_te) {
|
||||
if (insert_handle == dragged_te) {
|
||||
/* No need to do anything */
|
||||
return false;
|
||||
}
|
||||
@@ -138,8 +132,8 @@ static bool outliner_item_drag_drop_apply(const Scene *scene, OutlinerItemDrag *
|
||||
if (dragged_te->reinsert) {
|
||||
/* Not sure yet what the best way to handle reordering elements of different types
|
||||
* (and stored in different lists). For collection display mode this is enough. */
|
||||
if (!insert_after || (insert_after->reinsert == dragged_te->reinsert)) {
|
||||
dragged_te->reinsert(scene, dragged_te, insert_after, dragged_te->drag_data->insert_type);
|
||||
if (!insert_handle || (insert_handle->reinsert == dragged_te->reinsert)) {
|
||||
dragged_te->reinsert(scene, dragged_te, insert_handle, dragged_te->drag_data->insert_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +144,7 @@ static int outliner_item_drag_drop_modal(bContext *C, wmOperator *op, const wmEv
|
||||
{
|
||||
ARegion *ar = CTX_wm_region(C);
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
OutlinerItemDrag *op_drag_data = op->customdata;
|
||||
TreeElement *te_dragged = op->customdata;
|
||||
int retval = OPERATOR_RUNNING_MODAL;
|
||||
bool redraw = false;
|
||||
bool skip_rebuild = true;
|
||||
@@ -158,7 +152,7 @@ static int outliner_item_drag_drop_modal(bContext *C, wmOperator *op, const wmEv
|
||||
switch (event->type) {
|
||||
case EVT_MODAL_MAP:
|
||||
if (event->val == OUTLINER_ITEM_DRAG_CONFIRM) {
|
||||
outliner_item_drag_drop_apply(CTX_data_scene(C), op_drag_data);
|
||||
outliner_item_drag_drop_apply(CTX_data_scene(C), te_dragged);
|
||||
skip_rebuild = false;
|
||||
retval = OPERATOR_FINISHED;
|
||||
}
|
||||
@@ -169,11 +163,11 @@ static int outliner_item_drag_drop_modal(bContext *C, wmOperator *op, const wmEv
|
||||
BLI_assert(0);
|
||||
}
|
||||
WM_event_add_mousemove(C); /* update highlight */
|
||||
outliner_item_drag_end(op_drag_data);
|
||||
outliner_item_drag_end(te_dragged);
|
||||
redraw = true;
|
||||
break;
|
||||
case MOUSEMOVE:
|
||||
outliner_item_drag_handle(ar, event, op_drag_data);
|
||||
outliner_item_drag_handle(soops, ar, event, te_dragged);
|
||||
redraw = true;
|
||||
break;
|
||||
}
|
||||
@@ -192,17 +186,16 @@ static int outliner_item_drag_drop_invoke(bContext *C, wmOperator *op, const wmE
|
||||
{
|
||||
ARegion *ar = CTX_wm_region(C);
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
TreeElement *te = outliner_item_drag_element_find(soops, ar, event);
|
||||
TreeElement *te_dragged = outliner_item_drag_element_find(soops, ar, event);
|
||||
|
||||
if (!te) {
|
||||
if (!te_dragged) {
|
||||
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
|
||||
}
|
||||
|
||||
|
||||
op->customdata = outliner_item_drag_data_create(te, &event->x);
|
||||
te->drag_data = MEM_callocN(sizeof(*te->drag_data), __func__);
|
||||
op->customdata = te_dragged;
|
||||
te_dragged->drag_data = MEM_callocN(sizeof(*te_dragged->drag_data), __func__);
|
||||
/* by default we don't change the item position */
|
||||
te->drag_data->insert_handle = te;
|
||||
te_dragged->drag_data->insert_handle = te_dragged;
|
||||
/* unset highlighted tree element, dragged one will be highlighted instead */
|
||||
outliner_set_flag(&soops->tree, TSE_HIGHLIGHTED, false);
|
||||
|
||||
|
||||
@@ -1287,55 +1287,60 @@ static void outliner_add_orphaned_datablocks(Main *mainvar, SpaceOops *soops)
|
||||
}
|
||||
}
|
||||
|
||||
static void outliner_layer_collections_reorder(const Scene *scene, TreeElement *insert_element, TreeElement *insert_handle,
|
||||
TreeElementInsertType action)
|
||||
static void outliner_layer_collections_reorder(
|
||||
const Scene *scene, TreeElement *insert_element, TreeElement *insert_handle, TreeElementInsertType action)
|
||||
{
|
||||
LayerCollection *lc_src = insert_element->directdata;
|
||||
LayerCollection *lc_dst = insert_handle ? insert_handle->directdata : NULL;
|
||||
LayerCollection *lc_insert = insert_element->directdata;
|
||||
LayerCollection *lc_handle = insert_handle->directdata;
|
||||
|
||||
if (action == TE_INSERT_AFTER) {
|
||||
if (lc_dst == NULL) {
|
||||
/* It needs a LayerCollection to use as reference,
|
||||
* specially now that we are to allow insert in collections
|
||||
* that don't belong to the same hierarchical level */
|
||||
TODO_LAYER_OPERATORS;
|
||||
/* BKE_layer_collection_move_after(scene, sc_dst, sc_src); */
|
||||
}
|
||||
else {
|
||||
BKE_layer_collection_move_below(scene, lc_dst, lc_src);
|
||||
}
|
||||
if (action == TE_INSERT_BEFORE) {
|
||||
BKE_layer_collection_move_above(scene, lc_handle, lc_insert);
|
||||
}
|
||||
else if (action == TE_INSERT_AFTER) {
|
||||
BKE_layer_collection_move_below(scene, lc_handle, lc_insert);
|
||||
}
|
||||
else if (action == TE_INSERT_INTO) {
|
||||
BKE_layer_collection_move_into(scene, lc_dst, lc_src);
|
||||
BKE_layer_collection_move_into(scene, lc_insert, lc_handle);
|
||||
}
|
||||
else {
|
||||
BLI_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void outliner_scene_collections_reorder(const Scene *scene, TreeElement *insert_element, TreeElement *insert_handle,
|
||||
TreeElementInsertType action)
|
||||
static void outliner_scene_collections_reorder(
|
||||
const Scene *scene, TreeElement *insert_element, TreeElement *insert_handle, TreeElementInsertType action)
|
||||
{
|
||||
SceneCollection *sc_src = insert_element->directdata;
|
||||
SceneCollection *sc_dst = insert_handle ? insert_handle->directdata : NULL;
|
||||
SceneCollection *sc_master = BKE_collection_master(scene);
|
||||
SceneCollection *sc_insert = insert_element->directdata;
|
||||
SceneCollection *sc_handle = insert_handle->directdata;
|
||||
|
||||
if (action == TE_INSERT_AFTER) {
|
||||
if (sc_dst == NULL) {
|
||||
/* It needs a SceneCollection to use as reference,
|
||||
* specially now that we are to allow insert in collections
|
||||
* that don't belong to the same hierarchical level */
|
||||
TODO_LAYER_OPERATORS;
|
||||
/* BKE_collection_move_after(scene, sc_dst, sc_src); */
|
||||
if (sc_handle == sc_master) {
|
||||
/* exception: Can't insert before/after master selection, has to be one of its childs */
|
||||
if (action == TE_INSERT_BEFORE) {
|
||||
sc_handle = sc_master->scene_collections.first;
|
||||
}
|
||||
else {
|
||||
BKE_collection_move_below(scene, sc_dst, sc_src);
|
||||
else if (action == TE_INSERT_AFTER) {
|
||||
sc_handle = sc_master->scene_collections.last;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == TE_INSERT_BEFORE) {
|
||||
BKE_collection_move_above(scene, sc_handle, sc_insert);
|
||||
}
|
||||
else if (action == TE_INSERT_AFTER) {
|
||||
BKE_collection_move_below(scene, sc_handle, sc_insert);
|
||||
}
|
||||
else if (action == TE_INSERT_INTO) {
|
||||
BKE_collection_move_into(scene, sc_dst, sc_src);
|
||||
BKE_collection_move_into(scene, sc_handle, sc_insert);
|
||||
}
|
||||
else {
|
||||
BLI_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void outliner_add_layer_collections_recursive(SpaceOops *soops, ListBase *tree, Scene *scene,
|
||||
ListBase *layer_collections, TreeElement *parent_ten,
|
||||
int *io_collection_counter)
|
||||
static void outliner_add_layer_collections_recursive(
|
||||
SpaceOops *soops, ListBase *tree, Scene *scene, ListBase *layer_collections, TreeElement *parent_ten,
|
||||
int *io_collection_counter)
|
||||
{
|
||||
for (LayerCollection *collection = layer_collections->first; collection; collection = collection->next) {
|
||||
TreeElement *ten = outliner_add_element(soops, tree, scene, parent_ten, TSE_LAYER_COLLECTION,
|
||||
|
||||
Reference in New Issue
Block a user