Improve multiple drag and drop

This commit is contained in:
yds 2024-09-13 14:24:52 -03:00
parent 1742018696
commit 4df6647a75
3 changed files with 76 additions and 94 deletions

View File

@ -848,19 +848,10 @@ void LimboAIEditor::_on_tasks_dragged(const TypedArray<BTTask> &p_tasks, Ref<BTT
return; return;
} }
// Filter tasks // Remove descendants of selected.
Vector<Ref<BTTask>> tasks_list; Vector<Ref<BTTask>> tasks_list;
int no_effect = 0;
for (int i = 0; i < p_tasks.size(); i++) { for (int i = 0; i < p_tasks.size(); i++) {
Ref<BTTask> task = p_tasks[i]; Ref<BTTask> task = p_tasks[i];
// Count tasks that don't change position
if (task == p_to_task) {
if (Math::abs(task->get_index() - p_to_pos) <= 1) {
++no_effect;
}
}
// Remove descendants of selected
bool remove = false; bool remove = false;
for (int s_idx = 0; s_idx < p_tasks.size(); s_idx++) { for (int s_idx = 0; s_idx < p_tasks.size(); s_idx++) {
Ref<BTTask> selected = p_tasks[s_idx]; Ref<BTTask> selected = p_tasks[s_idx];
@ -873,48 +864,27 @@ void LimboAIEditor::_on_tasks_dragged(const TypedArray<BTTask> &p_tasks, Ref<BTT
tasks_list.push_back(task); tasks_list.push_back(task);
} }
} }
if (tasks_list.is_empty() || p_tasks.size() == no_effect) {
return;
}
EditorUndoRedoManager *undo_redo = _new_undo_redo_action(TTR("Drag BT Task")); EditorUndoRedoManager *undo_redo = _new_undo_redo_action(TTR("Drag BT Task"));
// Apply changes in the task hierarchy. // Remove all tasks first so adding ordering is stable.
int drop_idx = p_to_pos; int before_pos = 0;
for (const Ref<BTTask> &task : tasks_list) { for (const Ref<BTTask> task : tasks_list) {
if (task->get_parent() == p_to_task && drop_idx > task->get_index()) { if (task->get_parent() == p_to_task && p_to_pos > task->get_index()) {
drop_idx -= 1; before_pos += 1;
} }
if (task == p_to_task) {
if (Math::abs(task->get_index() - p_to_pos) <= 1) {
++drop_idx;
continue;
}
}
undo_redo->add_do_method(task->get_parent().ptr(), LW_NAME(remove_child), task); undo_redo->add_do_method(task->get_parent().ptr(), LW_NAME(remove_child), task);
}
undo_redo->add_do_method(p_to_task.ptr(), LW_NAME(add_child_at_index), task, drop_idx); for (int i = 0; i < tasks_list.size(); i++) {
Ref<BTTask> task = tasks_list[i];
undo_redo->add_do_method(p_to_task.ptr(), LW_NAME(add_child_at_index), task, p_to_pos + i - before_pos);
undo_redo->add_undo_method(p_to_task.ptr(), LW_NAME(remove_child), task); undo_redo->add_undo_method(p_to_task.ptr(), LW_NAME(remove_child), task);
++drop_idx;
} }
// Re-add tasks in later undo action so indexes match the old order. // Re-add tasks in later undo action so indexes match the old order.
drop_idx = p_to_pos; for (const Ref<BTTask> task : tasks_list) {
for (const Ref<BTTask> &task : tasks_list) {
if (task->get_parent() == p_to_task && drop_idx > task->get_index()) {
drop_idx -= 1;
}
if (task == p_to_task) {
if (Math::abs(task->get_index() - p_to_pos) <= 1) {
++drop_idx;
continue;
}
}
undo_redo->add_undo_method(task->get_parent().ptr(), LW_NAME(add_child_at_index), task, task->get_index()); undo_redo->add_undo_method(task->get_parent().ptr(), LW_NAME(add_child_at_index), task, task->get_index());
++drop_idx;
} }
_commit_action_with_update(undo_redo); _commit_action_with_update(undo_redo);

View File

@ -266,15 +266,7 @@ Vector<Ref<BTTask>> TaskTree::get_selected_tasks() const {
} }
void TaskTree::clear_selection() { void TaskTree::clear_selection() {
Vector<TreeItem*> selected_tasks; tree->deselect_all();
TreeItem *next = tree->get_next_selected(nullptr);
while (next) {
Ref<BTTask> task = next->get_metadata(0);
if (task.is_valid()) {
remove_selection(task);
}
next = tree->get_next_selected(next);
}
} }
Rect2 TaskTree::get_selected_probability_rect() const { Rect2 TaskTree::get_selected_probability_rect() const {
@ -384,7 +376,7 @@ bool TaskTree::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data) c
return false; return false;
} }
if (!item->get_parent() && section != 0) { // Before/after root item. if (!item->get_parent() && section < 0) { // Before root item.
return false; return false;
} }
@ -393,12 +385,18 @@ bool TaskTree::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data) c
if (tasks.is_empty()) { if (tasks.is_empty()) {
return false; // No tasks. return false; // No tasks.
} }
Ref<BTTask> to_task = item->get_metadata(0);
int to_pos = -1;
int type = tree->get_drop_section_at_position(p_point);
_normalize_drop(item, type, to_pos, to_task);
if (to_task.is_null()) {
return false; // Outside root.
}
for (int i = 0; i < tasks.size(); i++) { for (int i = 0; i < tasks.size(); i++) {
Ref<BTTask> task = tasks[i]; Ref<BTTask> task = tasks[i];
const Ref<BTTask> to_task = item->get_metadata(0); if (to_task->is_descendant_of(task) || task == to_task) {
if (to_task->is_descendant_of(task) || task == to_task || return false; // Don't drop as child of selected tasks.
(task == to_task && task->get_index() + section >= to_task->get_index() && !item->is_collapsed() && item->get_child_count() > 0)) {
return false; // Don't drop as child of itself.
} }
} }
} }
@ -408,55 +406,68 @@ bool TaskTree::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data) c
void TaskTree::_drop_data_fw(const Point2 &p_point, const Variant &p_data) { void TaskTree::_drop_data_fw(const Point2 &p_point, const Variant &p_data) {
Dictionary d = p_data; Dictionary d = p_data;
if (!d.has("tasks")) {
return;
}
TreeItem *item = tree->get_item_at_position(p_point); TreeItem *item = tree->get_item_at_position(p_point);
int type = tree->get_drop_section_at_position(p_point); int type = tree->get_drop_section_at_position(p_point);
ERR_FAIL_NULL(item); ERR_FAIL_NULL(item);
ERR_FAIL_COND(type < -1 || type > 1); ERR_FAIL_COND(type < -1 || type > 1);
if (item && d.has("tasks")) { // The drop behavior depends on the TreeItem's state.
TypedArray<BTTask> tasks = d["tasks"]; // Normalize and emit the parent task and position instead of exposing TreeItem.
int to_pos = -1; int to_pos = -1;
Ref<BTTask> to_task = item->get_metadata(0); Ref<BTTask> to_task = item->get_metadata(0);
ERR_FAIL_COND(to_task.is_null()); ERR_FAIL_COND(to_task.is_null());
_normalize_drop(item, type, to_pos, to_task);
emit_signal(LW_NAME(tasks_dragged), d["tasks"], to_task, to_pos);
}
// The drop behavior depends on the TreeItem's state. void TaskTree::_normalize_drop(TreeItem *item, int type, int &to_pos, Ref<BTTask> &to_task) const {
// Normalize and emit the parent task and position instead of exposing TreeItem. switch (type) {
switch (type) { case 0: // Drop as last child of target.
case 0: // Drop as last child of target. to_pos = to_task->get_child_count();
to_pos = to_task->get_child_count(); break;
break; case -1: // Drop above target.
case -1: // Drop above target. ERR_FAIL_COND_MSG(to_task->get_parent().is_null(), "Cannot perform drop above the root task!");
ERR_FAIL_COND_MSG(to_task->get_parent().is_null(), "Cannot perform drop above the root task!"); to_pos = to_task->get_index();
to_pos = MAX(0, to_task->get_index()); {
Vector<Ref<BTTask>> selected = get_selected_tasks();
if (to_task == selected[selected.size()-1]) {
to_pos += 1;
}
}
to_task = to_task->get_parent();
break;
case 1: // Drop below target.
if (item->get_child_count() == 0) {
to_pos = to_task->get_index() + 1;
if (to_task == tree->get_next_selected(nullptr)->get_metadata(0)) {
to_pos -= 1;
}
to_task = to_task->get_parent(); to_task = to_task->get_parent();
break; break;
case 1: // Drop below target. }
if (item->get_child_count() == 0) {
to_pos = to_task->get_index() + 1; if (to_task->get_parent().is_null() || !item->is_collapsed()) { // Insert as first child of target.
to_task = to_task->get_parent(); to_pos = 0;
break; } else { // Insert as sibling of target.
TreeItem *lower_sibling = nullptr;
for (int i = to_task->get_index() + 1; i < to_task->get_parent()->get_child_count(); i++) {
TreeItem *c = item->get_parent()->get_child(i);
if (c->is_visible_in_tree()) {
lower_sibling = c;
break;
}
}
if (lower_sibling) {
to_pos = lower_sibling->get_index();
} }
if (to_task->get_parent().is_null() || !item->is_collapsed()) { // Insert as first child of target. to_task = to_task->get_parent();
to_pos = 0; }
} else { // Insert as sibling of target. break;
TreeItem *lower_sibling = nullptr;
for (int i = to_task->get_index() + 1; i < to_task->get_parent()->get_child_count(); i++) {
TreeItem *c = item->get_parent()->get_child(i);
if (c->is_visible_in_tree()) {
lower_sibling = c;
break;
}
}
if (lower_sibling) {
to_pos = lower_sibling->get_index();
}
to_task = to_task->get_parent();
}
break;
}
emit_signal(LW_NAME(tasks_dragged), tasks, to_task, to_pos);
} }
} }

View File

@ -75,6 +75,7 @@ private:
Variant _get_drag_data_fw(const Point2 &p_point); Variant _get_drag_data_fw(const Point2 &p_point);
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data) const; bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data) const;
void _drop_data_fw(const Point2 &p_point, const Variant &p_data); void _drop_data_fw(const Point2 &p_point, const Variant &p_data);
void _normalize_drop(TreeItem *item, int type, int &to_pos, Ref<BTTask> &to_task) const;
void _draw_probability(Object *item_obj, Rect2 rect); void _draw_probability(Object *item_obj, Rect2 rect);