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:
Julian Eisel
2017-03-10 15:03:06 +01:00
parent b3bb4a6936
commit 8e303aae25
4 changed files with 99 additions and 122 deletions

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,