Improve multiple drag and drop

This commit is contained in:
yds 2024-09-13 14:24:52 -03:00
parent 1742018696
commit ed727198f1
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;
}
// Filter tasks
// Remove descendants of selected.
Vector<Ref<BTTask>> tasks_list;
int no_effect = 0;
for (int i = 0; i < p_tasks.size(); 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;
for (int s_idx = 0; s_idx < p_tasks.size(); 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);
}
}
if (tasks_list.is_empty() || p_tasks.size() == no_effect) {
return;
}
EditorUndoRedoManager *undo_redo = _new_undo_redo_action(TTR("Drag BT Task"));
// Apply changes in the task hierarchy.
int drop_idx = p_to_pos;
for (const Ref<BTTask> &task : tasks_list) {
if (task->get_parent() == p_to_task && drop_idx > task->get_index()) {
drop_idx -= 1;
// Remove all tasks first so adding ordering is stable.
int before_pos = 0;
for (const Ref<BTTask> task : tasks_list) {
if (task->get_parent() == p_to_task && p_to_pos > task->get_index()) {
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(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);
++drop_idx;
}
// 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) {
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;
}
}
for (const Ref<BTTask> task : tasks_list) {
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);

View File

@ -266,15 +266,7 @@ Vector<Ref<BTTask>> TaskTree::get_selected_tasks() const {
}
void TaskTree::clear_selection() {
Vector<TreeItem*> selected_tasks;
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);
}
tree->deselect_all();
}
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;
}
if (!item->get_parent() && section != 0) { // Before/after root item.
if (!item->get_parent() && section < 0) { // Before root item.
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()) {
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++) {
Ref<BTTask> task = tasks[i];
const Ref<BTTask> to_task = item->get_metadata(0);
if (to_task->is_descendant_of(task) || task == to_task ||
(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.
if (to_task->is_descendant_of(task) || task == to_task) {
return false; // Don't drop as child of selected tasks.
}
}
}
@ -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) {
Dictionary d = p_data;
if (!d.has("tasks")) {
return;
}
TreeItem *item = tree->get_item_at_position(p_point);
int type = tree->get_drop_section_at_position(p_point);
ERR_FAIL_NULL(item);
ERR_FAIL_COND(type < -1 || type > 1);
if (item && d.has("tasks")) {
TypedArray<BTTask> tasks = d["tasks"];
int to_pos = -1;
Ref<BTTask> to_task = item->get_metadata(0);
ERR_FAIL_COND(to_task.is_null());
// The drop behavior depends on the TreeItem's state.
// Normalize and emit the parent task and position instead of exposing TreeItem.
int to_pos = -1;
Ref<BTTask> to_task = item->get_metadata(0);
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.
// Normalize and emit the parent task and position instead of exposing TreeItem.
switch (type) {
case 0: // Drop as last child of target.
to_pos = to_task->get_child_count();
break;
case -1: // Drop above target.
ERR_FAIL_COND_MSG(to_task->get_parent().is_null(), "Cannot perform drop above the root task!");
to_pos = MAX(0, to_task->get_index());
void TaskTree::_normalize_drop(TreeItem *item, int type, int &to_pos, Ref<BTTask> &to_task) const {
switch (type) {
case 0: // Drop as last child of target.
to_pos = to_task->get_child_count();
break;
case -1: // Drop above target.
ERR_FAIL_COND_MSG(to_task->get_parent().is_null(), "Cannot perform drop above the root task!");
to_pos = MAX(0, to_task->get_index() - 1);
{
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();
break;
case 1: // Drop below target.
if (item->get_child_count() == 0) {
to_pos = to_task->get_index() + 1;
to_task = to_task->get_parent();
break;
}
if (to_task->get_parent().is_null() || !item->is_collapsed()) { // Insert as first child of target.
to_pos = 0;
} 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_pos = 0;
} 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();
}
to_task = to_task->get_parent();
}
break;
}
emit_signal(LW_NAME(tasks_dragged), tasks, to_task, to_pos);
to_task = to_task->get_parent();
}
break;
}
}

View File

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