From ed727198f120e1501a36067e3c64f8239779eebe Mon Sep 17 00:00:00 2001 From: yds Date: Fri, 13 Sep 2024 14:24:52 -0300 Subject: [PATCH] Improve multiple drag and drop --- editor/limbo_ai_editor_plugin.cpp | 52 +++---------- editor/task_tree.cpp | 117 ++++++++++++++++-------------- editor/task_tree.h | 1 + 3 files changed, 76 insertions(+), 94 deletions(-) diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 9da9d05..f5cc382 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -848,19 +848,10 @@ void LimboAIEditor::_on_tasks_dragged(const TypedArray &p_tasks, Ref> tasks_list; - int no_effect = 0; for (int i = 0; i < p_tasks.size(); i++) { Ref 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 selected = p_tasks[s_idx]; @@ -873,48 +864,27 @@ void LimboAIEditor::_on_tasks_dragged(const TypedArray &p_tasks, Ref &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 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 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 &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 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); diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 8e96e94..6cafc45 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -266,15 +266,7 @@ Vector> TaskTree::get_selected_tasks() const { } void TaskTree::clear_selection() { - Vector selected_tasks; - TreeItem *next = tree->get_next_selected(nullptr); - while (next) { - Ref 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 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 task = tasks[i]; - const Ref 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 tasks = d["tasks"]; - int to_pos = -1; - Ref 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 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 &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> 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; } } diff --git a/editor/task_tree.h b/editor/task_tree.h index 373f15d..24ba690 100644 --- a/editor/task_tree.h +++ b/editor/task_tree.h @@ -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 &to_task) const; void _draw_probability(Object *item_obj, Rect2 rect);