From 7c4a462a6934419565c861c105b3ae9395a5daa8 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Thu, 19 Sep 2024 23:35:14 +0200 Subject: [PATCH 01/45] Add ui for Tree search --- editor/limbo_ai_editor_plugin.cpp | 8 ++ editor/limbo_ai_editor_plugin.h | 3 + editor/task_tree.cpp | 14 +++- editor/task_tree.h | 6 ++ editor/tree_search.cpp | 119 ++++++++++++++++++++++++++++++ editor/tree_search.h | 84 +++++++++++++++++++++ register_types.cpp | 2 + util/limbo_string_names.cpp | 1 + util/limbo_string_names.h | 1 + 9 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 editor/tree_search.cpp create mode 100644 editor/tree_search.h diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 7374ff6..e2e9376 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -799,6 +799,9 @@ void LimboAIEditor::_misc_option_selected(int p_id) { EDITOR_FILE_SYSTEM()->scan(); EDIT_SCRIPT(template_path); } break; + case MISC_SEARCH_TREE: { + UtilityFunctions::print("Search Tree"); + } } } @@ -1319,6 +1322,9 @@ void LimboAIEditor::_update_misc_menu() { misc_menu->add_item( FILE_EXISTS(_get_script_template_path()) ? TTR("Edit Script Template") : TTR("Create Script Template"), MISC_CREATE_SCRIPT_TEMPLATE); + + misc_menu->add_separator(); + misc_menu->add_icon_item(theme_cache.search_icon, ("Search Tree"), MISC_SEARCH_TREE); } void LimboAIEditor::_update_banners() { @@ -1381,6 +1387,7 @@ void LimboAIEditor::_do_update_theme_item_cache() { theme_cache.cut_icon = get_theme_icon(LW_NAME(ActionCut), LW_NAME(EditorIcons)); theme_cache.copy_icon = get_theme_icon(LW_NAME(ActionCopy), LW_NAME(EditorIcons)); theme_cache.paste_icon = get_theme_icon(LW_NAME(ActionPaste), LW_NAME(EditorIcons)); + theme_cache.search_icon = get_theme_icon(LW_NAME(Search), LW_NAME(EditorIcons)); theme_cache.behavior_tree_icon = LimboUtility::get_singleton()->get_task_icon("BehaviorTree"); theme_cache.percent_icon = LimboUtility::get_singleton()->get_task_icon("LimboPercent"); @@ -1512,6 +1519,7 @@ LimboAIEditor::LimboAIEditor() { LW_SHORTCUT("limbo_ai/open_debugger", TTR("Open Debugger"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY_MASK(ALT) | LW_KEY(D))); LW_SHORTCUT("limbo_ai/jump_to_owner", TTR("Jump to Owner"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(J))); LW_SHORTCUT("limbo_ai/close_tab", TTR("Close Tab"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(W))); + LW_SHORTCUT("limbo_ai/find_task", TTR("Find Task"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F))); set_process_shortcut_input(true); diff --git a/editor/limbo_ai_editor_plugin.h b/editor/limbo_ai_editor_plugin.h index 4e13bb7..1868175 100644 --- a/editor/limbo_ai_editor_plugin.h +++ b/editor/limbo_ai_editor_plugin.h @@ -20,6 +20,7 @@ #include "owner_picker.h" #include "task_palette.h" #include "task_tree.h" +#include "tree_search.h" #ifdef LIMBOAI_MODULE #include "core/object/class_db.h" @@ -100,6 +101,7 @@ private: MISC_LAYOUT_WIDESCREEN_OPTIMIZED, MISC_PROJECT_SETTINGS, MISC_CREATE_SCRIPT_TEMPLATE, + MISC_SEARCH_TREE }; enum TabMenu { @@ -134,6 +136,7 @@ private: Ref cut_icon; Ref copy_icon; Ref paste_icon; + Ref search_icon; } theme_cache; EditorPlugin *plugin; diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index c44aff4..d70e4d8 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -17,6 +17,7 @@ #include "../bt/tasks/composites/bt_probability_selector.h" #include "../util/limbo_compat.h" #include "../util/limbo_utility.h" +#include "tree_search.h" #ifdef LIMBOAI_MODULE #include "core/object/script_language.h" @@ -124,6 +125,7 @@ void TaskTree::_update_tree() { for (const Ref &task : selection) { add_selection(task); } + tree_search.apply_search(tree); } TreeItem *TaskTree::_find_item(const Ref &p_task) const { @@ -565,9 +567,16 @@ void TaskTree::_bind_methods() { TaskTree::TaskTree() { editable = true; updating_tree = false; - + + // for Tree + TreeSearch, we want a VBoxContainer. For now, rather than changing this classes type, let's do nesting: + // TaskTree -> VBoxContainer -> [Tree, TreeSearchPanel] + VBoxContainer * vbox_container = memnew(VBoxContainer); + add_child(vbox_container); + vbox_container->set_anchors_preset(PRESET_FULL_RECT); + tree = memnew(Tree); - add_child(tree); + tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vbox_container->add_child(tree); tree->set_columns(2); tree->set_column_expand(0, true); tree->set_column_expand(1, false); @@ -578,6 +587,7 @@ TaskTree::TaskTree() { tree->set_select_mode(Tree::SelectMode::SELECT_MULTI); tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); + vbox_container->add_child(tree_search.search_panel); } TaskTree::~TaskTree() { diff --git a/editor/task_tree.h b/editor/task_tree.h index 24ba690..897a8b3 100644 --- a/editor/task_tree.h +++ b/editor/task_tree.h @@ -9,9 +9,13 @@ * ============================================================================= */ +#ifndef TASK_TREE_H +#define TASK_TREE_H + #ifdef TOOLS_ENABLED #include "../bt/behavior_tree.h" +#include "tree_search.h" #ifdef LIMBOAI_MODULE #include "scene/gui/control.h" @@ -42,6 +46,7 @@ private: bool editable; bool updating_tree; HashMap probability_rect_cache; + TreeSearch tree_search; struct ThemeCache { Ref comment_font; @@ -109,3 +114,4 @@ public: }; #endif // ! TOOLS_ENABLED +#endif // ! TASK_TREE_H diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp new file mode 100644 index 0000000..3b78d08 --- /dev/null +++ b/editor/tree_search.cpp @@ -0,0 +1,119 @@ +/** + * tree_search.cpp + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "tree_search.h" + +#include "../util/limbo_compat.h" // for edge scale +#include "../util/limbo_string_names.h" +#include "../util/limbo_utility.h" +#include +#include // for edge scale +#include + +#ifdef TOOLS_ENABLED + +/* ------- TreeSearchPanel ------- */ + +void TreeSearchPanel::_initialize_controls() { + line_edit_search = memnew(LineEdit); + check_button_filter_highlight = memnew(CheckButton); + close_button = memnew(Button); + label_highlight = memnew(Label); + label_filter = memnew(Label); + + line_edit_search->set_placeholder(TTR("Search tree")); + + label_highlight->set_text(TTR("Highlight")); + label_filter->set_text(TTR("Filter")); + + close_button->set_button_icon(get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); + // positioning and sizing + this->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); + this->set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically + + // hack add separator to the left so line edit doesn't clip. + line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); + + add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. + add_child(line_edit_search); + add_spacer(); + add_child(label_highlight); + add_child(check_button_filter_highlight); + add_child(label_filter); + add_child(close_button); + add_spacer(0.25); +} + +void TreeSearchPanel::add_spacer(float width_multiplier) { + Control *spacer = memnew(Control); + spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * width_multiplier, 0.0)); + add_child(spacer); +} + +/* !TreeSearchPanel */ + +TreeSearchPanel::TreeSearchPanel() { + _initialize_controls(); +} + +TreeSearchMode TreeSearchPanel::get_search_mode() { + if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { + return TreeSearchMode::HIGHLIGHT; + } + return TreeSearchMode::FILTER; +} + +String TreeSearchPanel::get_text() { + if (!line_edit_search) + return String(); + return line_edit_search->get_text(); +} + +/* ------- TreeSearch ------- */ + +void TreeSearch::filter_tree(TreeItem *tree_item, String search_mask) { + PRINT_LINE("filter tree not yet implemented!", search_mask); +} + +void TreeSearch::highlight_tree(TreeItem *tree_item, String search_mask) { + PRINT_LINE("highlight tree not yet implemented! ", search_mask); + // queue/iterative instead of recursive approach. dsf + Vector queue; + Vector hits; +} + +// Call this as a post-processing step for the already constructed tree. +void TreeSearch::apply_search(Tree *tree) { + if (!search_panel || !search_panel->is_visible()){ + return; + } + + TreeItem * tree_root = tree->get_root(); + String search_mask = search_panel->get_text(); + TreeSearchMode search_mode = search_panel->get_search_mode(); + if (search_mode == TreeSearchMode::HIGHLIGHT){ + highlight_tree(tree_root, search_mask); + } + if (search_mode == TreeSearchMode::FILTER){ + filter_tree(tree_root, search_mask); + } +} + +TreeSearch::TreeSearch() { + search_panel = memnew(TreeSearchPanel); +} + +TreeSearch::~TreeSearch() { +} + +/* !TreeSearch */ + +#endif // ! TOOLS_ENABLED \ No newline at end of file diff --git a/editor/tree_search.h b/editor/tree_search.h new file mode 100644 index 0000000..026fe87 --- /dev/null +++ b/editor/tree_search.h @@ -0,0 +1,84 @@ +/** + * tree_search.h + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifdef TOOLS_ENABLED + +#ifndef TREE_SEARCH_H +#define TREE_SEARCH_H + +#include "../bt/tasks/bt_task.h" // for tree item parsing + +#ifdef LIMBOAI_GDEXTENSION +#include +#include +#include +#include +#include +#include +#include +#endif // LIMBOAI_GDEXTENSION + +#ifdef LIMBOAI_MODULE +// TODO: Add includes for godot module variant. +#endif // LIMBOAI_MODULE + +using namespace godot; + +enum TreeSearchMode { + HIGHLIGHT = 0, + FILTER = 1 +}; + +class TreeSearchPanel : public HBoxContainer { +GDCLASS(TreeSearchPanel, HBoxContainer) +private: + Button *toggle_button_filter_highlight; + Button *close_button; + Label * label_highlight; + Label * label_filter; + LineEdit *line_edit_search; + CheckButton *check_button_filter_highlight; + + void _initialize_controls(); + void add_spacer(float width_multiplier = 1.f); + + + +protected: + static void _bind_methods(){}; // we don't need anything exposed. + +public: + TreeSearchMode get_search_mode(); + String get_text(); + + TreeSearchPanel(); +}; + +class TreeSearch { +private: + void filter_tree(TreeItem *tree_item, String search_mask); + void highlight_tree(TreeItem *tree_item, String search_mask); + + void highlight_item(TreeItem * tree_item, String search_mask); + Vector find_matching_entries(TreeItem * tree_item, Vector buffer = Vector()); + +public: + // we will add everything from TaskTree.h + void apply_search(Tree *tree); + void set_search_evaluation_method(Callable method); + TreeSearchPanel *search_panel; + + TreeSearch(); + ~TreeSearch(); +}; + +#endif // TREE_SEARCH_H +#endif // ! TOOLS_ENABLED diff --git a/register_types.cpp b/register_types.cpp index a04ef19..0eb4d60 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -106,6 +106,7 @@ #include "util/limbo_string_names.h" #include "util/limbo_task_db.h" #include "util/limbo_utility.h" +#include "editor/tree_search.h" #ifdef TOOLS_ENABLED #include "editor/debugger/behavior_tree_view.h" @@ -267,6 +268,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(OwnerPicker); GDREGISTER_CLASS(LimboAIEditor); GDREGISTER_CLASS(LimboAIEditorPlugin); + GDREGISTER_INTERNAL_CLASS(TreeSearchPanel); #endif // LIMBOAI_GDEXTENSION EditorPlugins::add_by_type(); diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index 5b0d80a..a5a20c7 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -46,6 +46,7 @@ LimboStringNames::LimboStringNames() { button_up = SN("button_up"); call_deferred = SN("call_deferred"); changed = SN("changed"); + Close = SN("Close"); dark_color_2 = SN("dark_color_2"); Debug = SN("Debug"); disabled_font_color = SN("disabled_font_color"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index a9237fc..f11af7d 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -62,6 +62,7 @@ public: StringName button_up; StringName call_deferred; StringName changed; + StringName Close; StringName dark_color_2; StringName Debug; StringName disabled_font_color; From a80b737319da1ffeead4c3e250f303d6844817ae Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 15:32:03 +0000 Subject: [PATCH 02/45] Implement tree search: highlighting --- editor/limbo_ai_editor_plugin.cpp | 6 +- editor/task_tree.cpp | 13 +- editor/task_tree.h | 4 +- editor/tree_search.cpp | 407 +++++++++++++++++++++++++++--- editor/tree_search.h | 81 ++++-- register_types.cpp | 1 + 6 files changed, 455 insertions(+), 57 deletions(-) diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index e2e9376..4300b4d 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -457,6 +457,8 @@ void LimboAIEditor::_process_shortcut_input(const Ref &p_event) { _on_save_pressed(); } else if (LW_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) { _popup_file_dialog(load_dialog); + } else if (LW_IS_SHORTCUT("limbo_ai/search_tree", p_event)) { + task_tree->tree_search_show_and_focus(); } else { handled = false; } @@ -800,7 +802,7 @@ void LimboAIEditor::_misc_option_selected(int p_id) { EDIT_SCRIPT(template_path); } break; case MISC_SEARCH_TREE: { - UtilityFunctions::print("Search Tree"); + task_tree->tree_search_show_and_focus(); } } } @@ -1520,6 +1522,8 @@ LimboAIEditor::LimboAIEditor() { LW_SHORTCUT("limbo_ai/jump_to_owner", TTR("Jump to Owner"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(J))); LW_SHORTCUT("limbo_ai/close_tab", TTR("Close Tab"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(W))); LW_SHORTCUT("limbo_ai/find_task", TTR("Find Task"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F))); + LW_SHORTCUT("limbo_ai/hide_tree_search", TTR("Hide BehaviorTrees Search Panel"), (Key)(LW_KEY(ESCAPE))); + LW_SHORTCUT("limbo_ai/search_tree", TTR("Shows the BehaviorTree Search Panel"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F))); set_process_shortcut_input(true); diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index d70e4d8..167ce1f 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -106,6 +106,7 @@ void TaskTree::_update_item(TreeItem *p_item) { if (!warning_text.is_empty()) { p_item->add_button(0, theme_cache.task_warning_icon, 0, false, warning_text); } + tree_search->on_item_edited(p_item); // this is necessary to preserve custom drawing from tree search. } void TaskTree::_update_tree() { @@ -125,7 +126,7 @@ void TaskTree::_update_tree() { for (const Ref &task : selection) { add_selection(task); } - tree_search.apply_search(tree); + tree_search->update_search(tree); } TreeItem *TaskTree::_find_item(const Ref &p_task) const { @@ -532,6 +533,8 @@ void TaskTree::_notification(int p_what) { tree->connect("multi_selected", callable_mp(this, &TaskTree::_on_item_selected).unbind(3), CONNECT_DEFERRED); tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_activated)); tree->connect("item_collapsed", callable_mp(this, &TaskTree::_on_item_collapsed)); + tree_search->search_panel->connect("text_changed", callable_mp(this, &TaskTree::_update_tree).unbind(1)); + tree_search->search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); } break; case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); @@ -564,6 +567,10 @@ void TaskTree::_bind_methods() { PropertyInfo(Variant::INT, "type"))); } +void TaskTree::tree_search_show_and_focus() { + tree_search->search_panel->show_and_focus(); +} + TaskTree::TaskTree() { editable = true; updating_tree = false; @@ -587,7 +594,9 @@ TaskTree::TaskTree() { tree->set_select_mode(Tree::SelectMode::SELECT_MULTI); tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); - vbox_container->add_child(tree_search.search_panel); + + tree_search = memnew(TreeSearch); + vbox_container->add_child(tree_search->search_panel); } TaskTree::~TaskTree() { diff --git a/editor/task_tree.h b/editor/task_tree.h index 897a8b3..e75ed72 100644 --- a/editor/task_tree.h +++ b/editor/task_tree.h @@ -46,7 +46,7 @@ private: bool editable; bool updating_tree; HashMap probability_rect_cache; - TreeSearch tree_search; + TreeSearch * tree_search; struct ThemeCache { Ref comment_font; @@ -101,6 +101,7 @@ public: Ref get_selected() const; Vector> get_selected_tasks() const; void clear_selection(); + void tree_search_show_and_focus(); Rect2 get_selected_probability_rect() const; double get_selected_probability_weight() const; @@ -108,6 +109,7 @@ public: bool selected_has_probability() const; virtual bool editor_can_reload_from_file() { return false; } + TaskTree(); ~TaskTree(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 3b78d08..23ba40e 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -9,16 +9,35 @@ * ============================================================================= */ +#ifdef TOOLS_ENABLED + #include "tree_search.h" -#include "../util/limbo_compat.h" // for edge scale +#include "../bt/behavior_tree.h" +#include "../util/limbo_compat.h" // for edscale #include "../util/limbo_string_names.h" #include "../util/limbo_utility.h" -#include -#include // for edge scale -#include -#ifdef TOOLS_ENABLED +#ifdef LIMBOAI_MODULE +#include "core/math/math_funcs.h" +#include "editor/editor_interface.h" +#include "editor/themes/editor_scale.h" +#include "scene/resources/font.h" +#include "scene/gui/separator.h" +#include "scene/resources/style_box_flat.h" +#include "scene/main/viewport.h" +#endif // LIMBOAI_MODULE + +#ifdef LIMBOAI_GDEXTENSION +#include +#include // for edge scale +#include +#include +#include +#include +#endif // LIMBOAI_GDEXTENSION + +#define UPPER_BOUND (1 << 15) // for substring search. /* ------- TreeSearchPanel ------- */ @@ -34,7 +53,9 @@ void TreeSearchPanel::_initialize_controls() { label_highlight->set_text(TTR("Highlight")); label_filter->set_text(TTR("Filter")); - close_button->set_button_icon(get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); + BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); + close_button->set_theme_type_variation("FlatButton"); + // positioning and sizing this->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); this->set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically @@ -42,26 +63,59 @@ void TreeSearchPanel::_initialize_controls() { // hack add separator to the left so line edit doesn't clip. line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); - add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. + _add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. add_child(line_edit_search); - add_spacer(); + _add_spacer(); add_child(label_highlight); add_child(check_button_filter_highlight); add_child(label_filter); add_child(close_button); - add_spacer(0.25); + _add_spacer(0.25); } -void TreeSearchPanel::add_spacer(float width_multiplier) { +void TreeSearchPanel::_initialize_close_callbacks() { + Callable calleable_set_invisible = Callable(this, "set_visible").bind(false); // don't need a custom bind. + close_button->connect("pressed", calleable_set_invisible); + close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); +} + +void TreeSearchPanel::_add_spacer(float p_width_multiplier) { Control *spacer = memnew(Control); - spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * width_multiplier, 0.0)); + spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * p_width_multiplier, 0.0)); add_child(spacer); } -/* !TreeSearchPanel */ +void TreeSearchPanel::_emit_text_changed(const String &p_text) { + this->emit_signal("text_changed", p_text); +} + +void TreeSearchPanel::_emit_text_submitted(const String &p_text) { + this->emit_signal("text_submitted"); +} + +void TreeSearchPanel::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + _initialize_controls(); + line_edit_search->connect("text_changed", callable_mp(this, &TreeSearchPanel::_emit_text_changed)); + _initialize_close_callbacks(); + line_edit_search->connect("text_submitted", callable_mp(this, &TreeSearchPanel::_emit_text_submitted)); + break; + } + } +} + +void TreeSearchPanel::_bind_methods() { + ADD_SIGNAL(MethodInfo("text_changed")); + ADD_SIGNAL(MethodInfo("text_submitted")); +} TreeSearchPanel::TreeSearchPanel() { - _initialize_controls(); + this->set_visible(false); +} + +bool TreeSearchPanel::has_focus() { + return false; } TreeSearchMode TreeSearchPanel::get_search_mode() { @@ -77,33 +131,324 @@ String TreeSearchPanel::get_text() { return line_edit_search->get_text(); } +void TreeSearchPanel::show_and_focus() { + this->set_visible(true); + line_edit_search->grab_focus(); +} + +/* !TreeSearchPanel */ + /* ------- TreeSearch ------- */ -void TreeSearch::filter_tree(TreeItem *tree_item, String search_mask) { - PRINT_LINE("filter tree not yet implemented!", search_mask); +void TreeSearch::_filter_tree(TreeItem *p_tree_item, const String &p_search_mask) { + for (int i = 0; i < ordered_tree_items.size(); i++){ + + } + PRINT_LINE("filter tree not yet implemented!", p_search_mask); } -void TreeSearch::highlight_tree(TreeItem *tree_item, String search_mask) { - PRINT_LINE("highlight tree not yet implemented! ", search_mask); - // queue/iterative instead of recursive approach. dsf - Vector queue; - Vector hits; +void TreeSearch::_highlight_tree(const String &p_search_mask) { + callable_cache.clear(); + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *entry = ordered_tree_items[i]; + + int num_m = number_matches.has(entry) ? number_matches.get(entry) : 0; + if (num_m == 0){ + continue;; + } + + // make sure to also call any draw method already defined. + Callable parent_draw_method; + if (entry->get_cell_mode(0) == TreeItem::CELL_MODE_CUSTOM) { + parent_draw_method = entry->get_custom_draw_callback(0); + } + + Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(p_search_mask, parent_draw_method); + + // -- this is necessary because of the modularity of this implementation + // cache render properties of entry + String cached_text = entry->get_text(0); + Ref cached_icon = entry->get_icon(0); + int cached_max_width = entry->get_icon_max_width(0); + callable_cache[entry] = draw_callback; + + // this removes render properties in entry + entry->set_custom_draw_callback(0, draw_callback); + entry->set_cell_mode(0, TreeItem::CELL_MODE_CUSTOM); + + // restore render properties + entry->set_text(0, cached_text); + entry->set_icon(0, cached_icon); + entry->set_icon_max_width(0, cached_max_width); + } } -// Call this as a post-processing step for the already constructed tree. -void TreeSearch::apply_search(Tree *tree) { - if (!search_panel || !search_panel->is_visible()){ +// custom draw callback for highlighting (bind 2) +void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, String p_search_mask, Callable p_parent_draw_method) { + if (!p_tree_item) + return; + // call any parent draw methods such as for probability FIRST. + p_parent_draw_method.call(p_tree_item, p_rect); + + // first part: outline + if (matching_entries.has(p_tree_item)) { + + // font info + Ref font = p_tree_item->get_custom_font(0); + if (font.is_null()) { + font = p_tree_item->get_tree()->get_theme_font(LW_NAME(font)); + } + ERR_FAIL_NULL(font); + double font_size = p_tree_item->get_custom_font_size(0); + if (font_size == -1) { + font_size = p_tree_item->get_tree()->get_theme_font_size(LW_NAME(font)); + } + + // substring size + String string_full = p_tree_item->get_text(0); + StringSearchIndices substring_idx = _substring_bounds(string_full, p_search_mask); + + String substring_match = string_full.substr(substring_idx.lower, substring_idx.upper - substring_idx.lower); + Vector2 substring_match_size = font->get_string_size(substring_match, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); + + String substring_before = string_full.substr(0, substring_idx.lower); + Vector2 substring_before_size = font->get_string_size(substring_before, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); + + // stylebox + Ref stylebox = p_tree_item->get_tree()->get_theme_stylebox("Focus"); + ERR_FAIL_NULL(stylebox); + + // extract separation + float h_sep = p_tree_item->get_tree()->get_theme_constant("h_separation"); + + // compose draw rect + const Vector2 PADDING = Vector2(4., 2.); + Rect2 draw_rect = p_rect; + + Vector2 rect_offset = Vector2(substring_before_size.x, 0); + rect_offset.x += p_tree_item->get_icon_max_width(0) * EDSCALE; + rect_offset.x += (h_sep + 4.) * EDSCALE; // TODO: Find better way to determine texts x-offset + rect_offset.y = (p_rect.size.y - substring_match_size.y) / 2; // center box vertically + + draw_rect.position += rect_offset - PADDING / 2; + draw_rect.size = substring_match_size + PADDING; + + // draw + stylebox->draw(p_tree_item->get_tree()->get_canvas_item(), draw_rect); + } + + // second part: draw number (TODO: maybe use columns) + int num_mat = number_matches.has(p_tree_item) ? number_matches.get(p_tree_item) : 0; + if (num_mat > 0) { + float h_sep = p_tree_item->get_tree()->get_theme_constant("h_separation"); + Ref font = tree_reference->get_theme_font("font"); + float font_size = tree_reference->get_theme_font_size("font") * 0.75; + + String num_string = String::num_int64(num_mat); + Vector2 string_size = font->get_string_size(num_string, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size); + Vector2 text_pos = p_rect.position; + + text_pos.x += p_rect.size.x - string_size.x - h_sep; + text_pos.y += font->get_descent(font_size) + p_rect.size.y / 2.; // center vertically + + font->draw_string(tree_reference->get_canvas_item(), text_pos, num_string, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size); + } +} + +void TreeSearch::_update_matching_entries(const String &search_mask) { + Vector accum; + matching_entries = _find_matching_entries(tree_reference->get_root(), search_mask, accum); +} + +/* this linearizes the tree into [ordered_tree_items] like so: + - i1 + - i2 + - i3 + - i4 ---> [i1,i2,i3,i4] +*/ +void TreeSearch::_update_ordered_tree_items(TreeItem *p_tree_item) { + if (!p_tree_item) + return; + if (p_tree_item == p_tree_item->get_tree()->get_root()) { + ordered_tree_items.clear(); + } + // Add the current item to the list + ordered_tree_items.push_back(p_tree_item); + + // Recursively collect items from the first child + TreeItem *child = p_tree_item->get_first_child(); + while (child) { + _update_ordered_tree_items(child); + child = child->get_next(); + } +} + +void TreeSearch::_update_number_matches() { + number_matches.clear(); + for (int i = 0; i < matching_entries.size(); i++) { + TreeItem *item = matching_entries[i]; + while (item) { + int old_num_value = number_matches.has(item) ? number_matches.get(item) : 0; + number_matches[item] = old_num_value + 1; + item = item->get_parent(); + } + } +} + +Vector TreeSearch::_find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) { + if (!p_tree_item) + return p_accum; + StringSearchIndices item_search_indices = _substring_bounds(p_tree_item->get_text(0), p_search_mask); + if (item_search_indices.hit()) + p_accum.insert(p_accum.bsearch(p_tree_item, true), p_tree_item); + + for (int i = 0; i < p_tree_item->get_child_count(); i++) { + TreeItem *child = p_tree_item->get_child(i); + _find_matching_entries(child, p_search_mask, p_accum); + } + return p_accum; +} + +// Returns the lower and upper bounds of a substring. Does fuzzy search: Simply looks if words exist in right ordering. +// Also ignores case if p_search_mask is lowercase. Example: +// p_searcheable = "TimeLimit 2 sec", p_search_mask = limit 2 sec -> [4,14]. With p_search_mask = "LimiT 2 SEC" or "Limit sec 2" -> [-1,-1] +TreeSearch::StringSearchIndices TreeSearch::_substring_bounds(const String &p_searchable, const String &p_search_mask) const { + StringSearchIndices result; + result.lower = UPPER_BOUND; + result.upper = 0; + + if (p_search_mask.is_empty()) { + return result; // Early return if search_mask is empty. + } + + // Determine if the search should be case-insensitive. + bool is_case_insensitive = (p_search_mask == p_search_mask.to_lower()); + String searchable_processed = is_case_insensitive ? p_searchable.to_lower() : p_searchable; + PackedStringArray words = p_search_mask.split(" "); + int word_position = 0; + for (const String &word : words) { + if (word.is_empty()) { + continue; // Skip empty words. + } + + String word_processed = is_case_insensitive ? word.to_lower() : word; + + // Find the position of the next word in the searchable string. + word_position = searchable_processed.find(word_processed, word_position); + + if (word_position < 0) { + // If any word is not found, return an empty StringSearchIndices. + return StringSearchIndices(); + } + + // Update lower and upper bounds. + result.lower = MIN(result.lower, word_position); + result.upper = MAX(result.upper, static_cast(word_position + word.length())); + } + + return result; +} + +void TreeSearch::_select_item(TreeItem *item) { + if (!item) + return; + tree_reference->set_selected(item, 0); + tree_reference->scroll_to_item(item); + item = item->get_parent(); + while (item) { + item->set_collapsed(false); + item = item->get_parent(); + } +} + +void TreeSearch::_select_first_match() { + if (matching_entries.size() == 0) { + return; + } + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *item = ordered_tree_items[i]; + int match_idx = matching_entries.bsearch(item, true); + if (match_idx < 0 || match_idx >= matching_entries.size() || matching_entries[match_idx] != item) { + continue; + } + String debug_string = "["; + _select_item(item); + return; + } +} + +void TreeSearch::_select_next_match() { + if (matching_entries.size() == 0) { + return; + } + TreeItem *selected = tree_reference->get_selected(); // we care about a single item here. + if (!selected) { + _select_first_match(); return; } - TreeItem * tree_root = tree->get_root(); - String search_mask = search_panel->get_text(); - TreeSearchMode search_mode = search_panel->get_search_mode(); - if (search_mode == TreeSearchMode::HIGHLIGHT){ - highlight_tree(tree_root, search_mask); + int selected_idx = -1; + for (int i = 0; i < ordered_tree_items.size(); i++) { + if (ordered_tree_items[i] == selected) { + selected_idx = i; + break; + } } - if (search_mode == TreeSearchMode::FILTER){ - filter_tree(tree_root, search_mask); + + // find the best fitting entry. + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *item = ordered_tree_items[i]; + int match_idx = matching_entries.bsearch(item, true); + if (match_idx < 0 || match_idx >= matching_entries.size() || selected_idx >= i || matching_entries[match_idx] != item) { + continue; + } + + _select_item(item); + return; + } + _select_first_match(); // wrap around. +} + +void TreeSearch::on_item_edited(TreeItem * item) { + if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM){ + return; + } + + if (!callable_cache.has(item) || item->get_custom_draw_callback(0) == callable_cache.get(item)){ + return; + } + + item->set_custom_draw_callback(0,callable_cache.get(item)); +} + +// Call this as a post-processing step for the already constructed tree. +void TreeSearch::update_search(Tree *p_tree) { + ERR_FAIL_COND(!search_panel || !p_tree); + + // ignore if panel not visible or no search string is given. + if (!search_panel->is_visible() || search_panel->get_text().length() == 0) { + return; + } + + tree_reference = p_tree; + + String search_mask = search_panel->get_text(); + TreeItem *tree_root = p_tree->get_root(); + TreeSearchMode search_mode = search_panel->get_search_mode(); + + _update_ordered_tree_items(p_tree->get_root()); + _update_matching_entries(search_mask); + _update_number_matches(); + + + if (search_mode == TreeSearchMode::HIGHLIGHT) { + _highlight_tree(search_mask); + if (!search_panel->is_connected("text_submitted", callable_mp(this, &TreeSearch::_select_next_match))) { + search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); + } + } + if (search_mode == TreeSearchMode::FILTER) { + _filter_tree(tree_root, search_mask); } } @@ -116,4 +461,4 @@ TreeSearch::~TreeSearch() { /* !TreeSearch */ -#endif // ! TOOLS_ENABLED \ No newline at end of file +#endif // TOOLS_ENABLED \ No newline at end of file diff --git a/editor/tree_search.h b/editor/tree_search.h index 026fe87..5786cb1 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -18,62 +18,99 @@ #ifdef LIMBOAI_GDEXTENSION #include -#include #include +#include #include #include -#include -#include + #endif // LIMBOAI_GDEXTENSION #ifdef LIMBOAI_MODULE -// TODO: Add includes for godot module variant. +#include "scene/gui/check_button.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/tree.h" #endif // LIMBOAI_MODULE using namespace godot; enum TreeSearchMode { - HIGHLIGHT = 0, - FILTER = 1 + HIGHLIGHT = 0, + FILTER = 1 }; class TreeSearchPanel : public HBoxContainer { -GDCLASS(TreeSearchPanel, HBoxContainer) + GDCLASS(TreeSearchPanel, HBoxContainer) private: Button *toggle_button_filter_highlight; Button *close_button; - Label * label_highlight; - Label * label_filter; + Label *label_highlight; + Label *label_filter; LineEdit *line_edit_search; CheckButton *check_button_filter_highlight; - void _initialize_controls(); - void add_spacer(float width_multiplier = 1.f); - + void _initialize_close_callbacks(); + void _add_spacer(float width_multiplier = 1.f); + void _on_draw_highlight(TreeItem *item, Rect2 rect); + void _emit_text_changed(const String &text); + void _emit_text_submitted(const String &p_text); + void _notification(int p_what); protected: - static void _bind_methods(){}; // we don't need anything exposed. + static void _bind_methods(); + void _process_shortcut_input(const Ref &p_event); public: TreeSearchMode get_search_mode(); String get_text(); - + void show_and_focus(); TreeSearchPanel(); + bool has_focus(); }; -class TreeSearch { +class TreeSearch : public RefCounted { + GDCLASS(TreeSearch, RefCounted) private: - void filter_tree(TreeItem *tree_item, String search_mask); - void highlight_tree(TreeItem *tree_item, String search_mask); + struct StringSearchIndices { + // initialize to opposite bounds. + int lower = -1; + int upper = -1; - void highlight_item(TreeItem * tree_item, String search_mask); - Vector find_matching_entries(TreeItem * tree_item, Vector buffer = Vector()); + bool hit() { + return 0 <= lower && lower < upper; + } + }; + + Tree * tree_reference; + + Vector ordered_tree_items; + Vector matching_entries; + HashMap number_matches; + HashMap callable_cache; + + void _filter_tree(TreeItem *tree_item, const String &search_mask); + void _highlight_tree(const String &p_search_mask); + void _draw_highlight_item(TreeItem *tree_item, Rect2 rect, String search_mask, Callable parent_draw_method); + void _update_matching_entries(const String &search_mask); + void _update_ordered_tree_items(TreeItem *tree_item); + void _update_number_matches(); + Vector _find_matching_entries(TreeItem *tree_item, const String &search_mask, Vector &buffer); + StringSearchIndices _substring_bounds(const String &searchable, const String &search_mask) const; + + void _select_item(TreeItem * item); + void _select_first_match(); + void _select_next_match(); + + +protected: + static void _bind_methods(){} public: // we will add everything from TaskTree.h - void apply_search(Tree *tree); - void set_search_evaluation_method(Callable method); + void update_search(Tree *tree); + void on_item_edited(TreeItem *item); TreeSearchPanel *search_panel; TreeSearch(); @@ -81,4 +118,4 @@ public: }; #endif // TREE_SEARCH_H -#endif // ! TOOLS_ENABLED +#endif // ! TOOLS_ENABLED \ No newline at end of file diff --git a/register_types.cpp b/register_types.cpp index 0eb4d60..162df4b 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -269,6 +269,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(LimboAIEditor); GDREGISTER_CLASS(LimboAIEditorPlugin); GDREGISTER_INTERNAL_CLASS(TreeSearchPanel); + GDREGISTER_INTERNAL_CLASS(TreeSearch); #endif // LIMBOAI_GDEXTENSION EditorPlugins::add_by_type(); From f523af2d13bea14e09ba8dcae92c09ae5f67505c Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 16:15:17 +0000 Subject: [PATCH 03/45] Make TreeSearchPanel HFlowContainer --- editor/tree_search.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/tree_search.h b/editor/tree_search.h index 5786cb1..80ddd0d 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -18,7 +18,7 @@ #ifdef LIMBOAI_GDEXTENSION #include -#include +#include #include #include #include @@ -27,7 +27,7 @@ #ifdef LIMBOAI_MODULE #include "scene/gui/check_button.h" -#include "scene/gui/box_container.h" +#include "scene/gui/flow_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/tree.h" @@ -40,8 +40,8 @@ enum TreeSearchMode { FILTER = 1 }; -class TreeSearchPanel : public HBoxContainer { - GDCLASS(TreeSearchPanel, HBoxContainer) +class TreeSearchPanel : public HFlowContainer { + GDCLASS(TreeSearchPanel, HFlowContainer) private: Button *toggle_button_filter_highlight; Button *close_button; From b4974bffd28860b872ae3d585e3618587279796b Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 16:17:44 +0000 Subject: [PATCH 04/45] Run clang-format --- editor/task_tree.cpp | 18 +++++++++--------- editor/tree_search.cpp | 30 ++++++++++++++---------------- editor/tree_search.h | 9 ++++----- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 167ce1f..ca8e4fe 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -23,17 +23,17 @@ #include "core/object/script_language.h" #include "editor/themes/editor_scale.h" #include "scene/gui/box_container.h" -#include "scene/gui/texture_rect.h" #include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION #include -#include #include -#include -#include #include +#include +#include +#include using namespace godot; #endif // LIMBOAI_GDEXTENSION @@ -437,7 +437,7 @@ void TaskTree::_normalize_drop(TreeItem *item, int type, int &to_pos, Refget_index(); { Vector> selected = get_selected_tasks(); - if (to_task == selected[selected.size()-1]) { + if (to_task == selected[selected.size() - 1]) { to_pos += 1; } } @@ -574,13 +574,13 @@ void TaskTree::tree_search_show_and_focus() { TaskTree::TaskTree() { editable = true; updating_tree = false; - + // for Tree + TreeSearch, we want a VBoxContainer. For now, rather than changing this classes type, let's do nesting: // TaskTree -> VBoxContainer -> [Tree, TreeSearchPanel] - VBoxContainer * vbox_container = memnew(VBoxContainer); + VBoxContainer *vbox_container = memnew(VBoxContainer); add_child(vbox_container); vbox_container->set_anchors_preset(PRESET_FULL_RECT); - + tree = memnew(Tree); tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbox_container->add_child(tree); @@ -594,7 +594,7 @@ TaskTree::TaskTree() { tree->set_select_mode(Tree::SelectMode::SELECT_MULTI); tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); - + tree_search = memnew(TreeSearch); vbox_container->add_child(tree_search->search_panel); } diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 23ba40e..faa5763 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -22,19 +22,19 @@ #include "core/math/math_funcs.h" #include "editor/editor_interface.h" #include "editor/themes/editor_scale.h" -#include "scene/resources/font.h" #include "scene/gui/separator.h" -#include "scene/resources/style_box_flat.h" #include "scene/main/viewport.h" +#include "scene/resources/font.h" +#include "scene/resources/style_box_flat.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION -#include #include // for edge scale #include #include #include #include +#include #endif // LIMBOAI_GDEXTENSION #define UPPER_BOUND (1 << 15) // for substring search. @@ -141,8 +141,7 @@ void TreeSearchPanel::show_and_focus() { /* ------- TreeSearch ------- */ void TreeSearch::_filter_tree(TreeItem *p_tree_item, const String &p_search_mask) { - for (int i = 0; i < ordered_tree_items.size(); i++){ - + for (int i = 0; i < ordered_tree_items.size(); i++) { } PRINT_LINE("filter tree not yet implemented!", p_search_mask); } @@ -153,8 +152,9 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) { TreeItem *entry = ordered_tree_items[i]; int num_m = number_matches.has(entry) ? number_matches.get(entry) : 0; - if (num_m == 0){ - continue;; + if (num_m == 0) { + continue; + ; } // make sure to also call any draw method already defined. @@ -164,7 +164,7 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) { } Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(p_search_mask, parent_draw_method); - + // -- this is necessary because of the modularity of this implementation // cache render properties of entry String cached_text = entry->get_text(0); @@ -189,10 +189,9 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Strin return; // call any parent draw methods such as for probability FIRST. p_parent_draw_method.call(p_tree_item, p_rect); - + // first part: outline if (matching_entries.has(p_tree_item)) { - // font info Ref font = p_tree_item->get_custom_font(0); if (font.is_null()) { @@ -207,7 +206,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Strin // substring size String string_full = p_tree_item->get_text(0); StringSearchIndices substring_idx = _substring_bounds(string_full, p_search_mask); - + String substring_match = string_full.substr(substring_idx.lower, substring_idx.upper - substring_idx.lower); Vector2 substring_match_size = font->get_string_size(substring_match, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); @@ -409,16 +408,16 @@ void TreeSearch::_select_next_match() { _select_first_match(); // wrap around. } -void TreeSearch::on_item_edited(TreeItem * item) { - if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM){ +void TreeSearch::on_item_edited(TreeItem *item) { + if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) { return; } - if (!callable_cache.has(item) || item->get_custom_draw_callback(0) == callable_cache.get(item)){ + if (!callable_cache.has(item) || item->get_custom_draw_callback(0) == callable_cache.get(item)) { return; } - item->set_custom_draw_callback(0,callable_cache.get(item)); + item->set_custom_draw_callback(0, callable_cache.get(item)); } // Call this as a post-processing step for the already constructed tree. @@ -440,7 +439,6 @@ void TreeSearch::update_search(Tree *p_tree) { _update_matching_entries(search_mask); _update_number_matches(); - if (search_mode == TreeSearchMode::HIGHLIGHT) { _highlight_tree(search_mask); if (!search_panel->is_connected("text_submitted", callable_mp(this, &TreeSearch::_select_next_match))) { diff --git a/editor/tree_search.h b/editor/tree_search.h index 80ddd0d..0b4300f 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -83,13 +83,13 @@ private: } }; - Tree * tree_reference; + Tree *tree_reference; Vector ordered_tree_items; Vector matching_entries; HashMap number_matches; HashMap callable_cache; - + void _filter_tree(TreeItem *tree_item, const String &search_mask); void _highlight_tree(const String &p_search_mask); void _draw_highlight_item(TreeItem *tree_item, Rect2 rect, String search_mask, Callable parent_draw_method); @@ -99,13 +99,12 @@ private: Vector _find_matching_entries(TreeItem *tree_item, const String &search_mask, Vector &buffer); StringSearchIndices _substring_bounds(const String &searchable, const String &search_mask) const; - void _select_item(TreeItem * item); + void _select_item(TreeItem *item); void _select_first_match(); void _select_next_match(); - protected: - static void _bind_methods(){} + static void _bind_methods() {} public: // we will add everything from TaskTree.h From 18a6bbeae61b45394164e398c48b86ed695b3c40 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 16:54:33 +0000 Subject: [PATCH 05/45] Implement filtering for TreeSearch --- editor/task_tree.cpp | 2 ++ editor/tree_search.cpp | 44 ++++++++++++++++++++++++++++++++++-------- editor/tree_search.h | 11 +++++++++-- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index ca8e4fe..3a0e008 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -533,8 +533,10 @@ void TaskTree::_notification(int p_what) { tree->connect("multi_selected", callable_mp(this, &TaskTree::_on_item_selected).unbind(3), CONNECT_DEFERRED); tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_activated)); tree->connect("item_collapsed", callable_mp(this, &TaskTree::_on_item_collapsed)); + // TODO: Simplify these signals into one (candidate names: changed, updated, update_requested): tree_search->search_panel->connect("text_changed", callable_mp(this, &TaskTree::_update_tree).unbind(1)); tree_search->search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); + tree_search->search_panel->connect("filter_toggled", callable_mp(this, &TaskTree::_update_tree)); } break; case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index faa5763..5dfcf76 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -93,6 +93,10 @@ void TreeSearchPanel::_emit_text_submitted(const String &p_text) { this->emit_signal("text_submitted"); } +void TreeSearchPanel::_emit_filter_toggled() { + this->emit_signal("filter_toggled"); +} + void TreeSearchPanel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -100,6 +104,7 @@ void TreeSearchPanel::_notification(int p_what) { line_edit_search->connect("text_changed", callable_mp(this, &TreeSearchPanel::_emit_text_changed)); _initialize_close_callbacks(); line_edit_search->connect("text_submitted", callable_mp(this, &TreeSearchPanel::_emit_text_submitted)); + check_button_filter_highlight->connect("pressed", callable_mp(this, &TreeSearchPanel::_emit_filter_toggled)); break; } } @@ -108,6 +113,7 @@ void TreeSearchPanel::_notification(int p_what) { void TreeSearchPanel::_bind_methods() { ADD_SIGNAL(MethodInfo("text_changed")); ADD_SIGNAL(MethodInfo("text_submitted")); + ADD_SIGNAL(MethodInfo("filter_toggled")); } TreeSearchPanel::TreeSearchPanel() { @@ -140,10 +146,26 @@ void TreeSearchPanel::show_and_focus() { /* ------- TreeSearch ------- */ -void TreeSearch::_filter_tree(TreeItem *p_tree_item, const String &p_search_mask) { - for (int i = 0; i < ordered_tree_items.size(); i++) { +void TreeSearch::_filter_tree(const String &p_search_mask) { + if (matching_entries.size() == 0) { + return; + } + + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *cur_item = ordered_tree_items[i]; + + if (number_matches.has(cur_item)){ + continue; + } + + TreeItem *first_counting_ancestor = cur_item; + while (first_counting_ancestor && !number_matches.has(first_counting_ancestor)) { + first_counting_ancestor = first_counting_ancestor->get_parent(); + } + if (!first_counting_ancestor || first_counting_ancestor == tree_reference->get_root() || !_vector_has_bsearch(matching_entries, first_counting_ancestor)) { + cur_item->set_visible(false); + } } - PRINT_LINE("filter tree not yet implemented!", p_search_mask); } void TreeSearch::_highlight_tree(const String &p_search_mask) { @@ -366,8 +388,7 @@ void TreeSearch::_select_first_match() { } for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; - int match_idx = matching_entries.bsearch(item, true); - if (match_idx < 0 || match_idx >= matching_entries.size() || matching_entries[match_idx] != item) { + if (!_vector_has_bsearch(matching_entries, item)){ continue; } String debug_string = "["; @@ -397,8 +418,7 @@ void TreeSearch::_select_next_match() { // find the best fitting entry. for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; - int match_idx = matching_entries.bsearch(item, true); - if (match_idx < 0 || match_idx >= matching_entries.size() || selected_idx >= i || matching_entries[match_idx] != item) { + if (!_vector_has_bsearch(matching_entries, item)){ continue; } @@ -408,6 +428,14 @@ void TreeSearch::_select_next_match() { _select_first_match(); // wrap around. } +template +inline bool TreeSearch::_vector_has_bsearch(Vector p_vec, T *element) { + int idx = p_vec.bsearch(element, true); + bool in_array = idx >= 0 && idx < p_vec.size(); + + return in_array && p_vec[idx] == element; +} + void TreeSearch::on_item_edited(TreeItem *item) { if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) { return; @@ -446,7 +474,7 @@ void TreeSearch::update_search(Tree *p_tree) { } } if (search_mode == TreeSearchMode::FILTER) { - _filter_tree(tree_root, search_mask); + _filter_tree(search_mask); } } diff --git a/editor/tree_search.h b/editor/tree_search.h index 0b4300f..6c13a4b 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -56,6 +56,7 @@ private: void _on_draw_highlight(TreeItem *item, Rect2 rect); void _emit_text_changed(const String &text); void _emit_text_submitted(const String &p_text); + void _emit_filter_toggled(); void _notification(int p_what); protected: @@ -90,12 +91,14 @@ private: HashMap number_matches; HashMap callable_cache; - void _filter_tree(TreeItem *tree_item, const String &search_mask); + void _filter_tree(const String &p_search_mask); void _highlight_tree(const String &p_search_mask); void _draw_highlight_item(TreeItem *tree_item, Rect2 rect, String search_mask, Callable parent_draw_method); void _update_matching_entries(const String &search_mask); void _update_ordered_tree_items(TreeItem *tree_item); void _update_number_matches(); + + Vector _find_matching_entries(TreeItem *tree_item, const String &search_mask, Vector &buffer); StringSearchIndices _substring_bounds(const String &searchable, const String &search_mask) const; @@ -103,6 +106,8 @@ private: void _select_first_match(); void _select_next_match(); + template + bool _vector_has_bsearch(Vector p_vec, T* element); protected: static void _bind_methods() {} @@ -117,4 +122,6 @@ public: }; #endif // TREE_SEARCH_H -#endif // ! TOOLS_ENABLED \ No newline at end of file +#endif // ! TOOLS_ENABLED + + From 8c323956e3afe74243fff791ff807167bd913baf Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 17:03:46 +0000 Subject: [PATCH 06/45] Unify highlight and filter, simplify ui --- editor/tree_search.cpp | 22 +++++++++------------- editor/tree_search.h | 7 +++---- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 5dfcf76..dae4aa3 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -43,14 +43,12 @@ void TreeSearchPanel::_initialize_controls() { line_edit_search = memnew(LineEdit); - check_button_filter_highlight = memnew(CheckButton); + check_button_filter_highlight = memnew(CheckBox); close_button = memnew(Button); - label_highlight = memnew(Label); label_filter = memnew(Label); line_edit_search->set_placeholder(TTR("Search tree")); - label_highlight->set_text(TTR("Highlight")); label_filter->set_text(TTR("Filter")); BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); @@ -65,10 +63,9 @@ void TreeSearchPanel::_initialize_controls() { _add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. add_child(line_edit_search); - _add_spacer(); - add_child(label_highlight); add_child(check_button_filter_highlight); add_child(label_filter); + _add_spacer(); add_child(close_button); _add_spacer(0.25); } @@ -154,7 +151,7 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *cur_item = ordered_tree_items[i]; - if (number_matches.has(cur_item)){ + if (number_matches.has(cur_item)) { continue; } @@ -388,7 +385,7 @@ void TreeSearch::_select_first_match() { } for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; - if (!_vector_has_bsearch(matching_entries, item)){ + if (!_vector_has_bsearch(matching_entries, item)) { continue; } String debug_string = "["; @@ -418,7 +415,7 @@ void TreeSearch::_select_next_match() { // find the best fitting entry. for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; - if (!_vector_has_bsearch(matching_entries, item)){ + if (!_vector_has_bsearch(matching_entries, item)) { continue; } @@ -467,12 +464,11 @@ void TreeSearch::update_search(Tree *p_tree) { _update_matching_entries(search_mask); _update_number_matches(); - if (search_mode == TreeSearchMode::HIGHLIGHT) { - _highlight_tree(search_mask); - if (!search_panel->is_connected("text_submitted", callable_mp(this, &TreeSearch::_select_next_match))) { - search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); - } + _highlight_tree(search_mask); + if (!search_panel->is_connected("text_submitted", callable_mp(this, &TreeSearch::_select_next_match))) { + search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); } + if (search_mode == TreeSearchMode::FILTER) { _filter_tree(search_mask); } diff --git a/editor/tree_search.h b/editor/tree_search.h index 6c13a4b..6b4c8ef 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -17,7 +17,7 @@ #include "../bt/tasks/bt_task.h" // for tree item parsing #ifdef LIMBOAI_GDEXTENSION -#include +#include #include #include #include @@ -26,7 +26,7 @@ #endif // LIMBOAI_GDEXTENSION #ifdef LIMBOAI_MODULE -#include "scene/gui/check_button.h" +#include "scene/gui/check_box.h" #include "scene/gui/flow_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" @@ -45,10 +45,9 @@ class TreeSearchPanel : public HFlowContainer { private: Button *toggle_button_filter_highlight; Button *close_button; - Label *label_highlight; Label *label_filter; LineEdit *line_edit_search; - CheckButton *check_button_filter_highlight; + CheckBox *check_button_filter_highlight; void _initialize_controls(); void _initialize_close_callbacks(); void _add_spacer(float width_multiplier = 1.f); From aed889276076085143d8b6f3eabf18d2de3b6adc Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 17:13:13 +0000 Subject: [PATCH 07/45] Fix regression from 62f8e: Select next item --- editor/tree_search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index dae4aa3..14cbfad 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -415,7 +415,7 @@ void TreeSearch::_select_next_match() { // find the best fitting entry. for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; - if (!_vector_has_bsearch(matching_entries, item)) { + if (!_vector_has_bsearch(matching_entries, item) || selected_idx >= i) { continue; } @@ -457,7 +457,6 @@ void TreeSearch::update_search(Tree *p_tree) { tree_reference = p_tree; String search_mask = search_panel->get_text(); - TreeItem *tree_root = p_tree->get_root(); TreeSearchMode search_mode = search_panel->get_search_mode(); _update_ordered_tree_items(p_tree->get_root()); From b8259c6ee7e2d342eb4d17c760276d887f312f4a Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 17:34:44 +0000 Subject: [PATCH 08/45] Address memory leak --- editor/task_tree.cpp | 2 +- editor/task_tree.h | 2 +- editor/tree_search.h | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 3a0e008..bdeea48 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -597,7 +597,7 @@ TaskTree::TaskTree() { tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); - tree_search = memnew(TreeSearch); + tree_search.instantiate(); vbox_container->add_child(tree_search->search_panel); } diff --git a/editor/task_tree.h b/editor/task_tree.h index e75ed72..30564c6 100644 --- a/editor/task_tree.h +++ b/editor/task_tree.h @@ -46,7 +46,7 @@ private: bool editable; bool updating_tree; HashMap probability_rect_cache; - TreeSearch * tree_search; + Ref tree_search; struct ThemeCache { Ref comment_font; diff --git a/editor/tree_search.h b/editor/tree_search.h index 6b4c8ef..8f91dd2 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -60,7 +60,6 @@ private: protected: static void _bind_methods(); - void _process_shortcut_input(const Ref &p_event); public: TreeSearchMode get_search_mode(); From 8d29f16963c30dcd073964b028ff6d5f1303b952 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Wed, 25 Sep 2024 17:45:59 +0000 Subject: [PATCH 09/45] Fix filtering: Allow matching for root as ancestor --- editor/tree_search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 14cbfad..92ec519 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -159,7 +159,7 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { while (first_counting_ancestor && !number_matches.has(first_counting_ancestor)) { first_counting_ancestor = first_counting_ancestor->get_parent(); } - if (!first_counting_ancestor || first_counting_ancestor == tree_reference->get_root() || !_vector_has_bsearch(matching_entries, first_counting_ancestor)) { + if (!first_counting_ancestor || !_vector_has_bsearch(matching_entries, first_counting_ancestor)) { cur_item->set_visible(false); } } From 1145ce0252e51c3bad10c44c6db365f22269efcd Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Thu, 26 Sep 2024 05:28:59 +0000 Subject: [PATCH 10/45] Clean up TreeSearch: Consistent p_params, -Destructor --- editor/tree_search.cpp | 31 ++++++++++++++++--------------- editor/tree_search.h | 21 ++++++++++----------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 92ec519..d1b0bdf 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -173,7 +173,6 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) { int num_m = number_matches.has(entry) ? number_matches.get(entry) : 0; if (num_m == 0) { continue; - ; } // make sure to also call any draw method already defined. @@ -217,7 +216,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Strin font = p_tree_item->get_tree()->get_theme_font(LW_NAME(font)); } ERR_FAIL_NULL(font); - double font_size = p_tree_item->get_custom_font_size(0); + float font_size = p_tree_item->get_custom_font_size(0); if (font_size == -1) { font_size = p_tree_item->get_tree()->get_theme_font_size(LW_NAME(font)); } @@ -273,9 +272,9 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Strin } } -void TreeSearch::_update_matching_entries(const String &search_mask) { +void TreeSearch::_update_matching_entries(const String &p_search_mask) { Vector accum; - matching_entries = _find_matching_entries(tree_reference->get_root(), search_mask, accum); + matching_entries = _find_matching_entries(tree_reference->get_root(), p_search_mask, accum); } /* this linearizes the tree into [ordered_tree_items] like so: @@ -367,16 +366,21 @@ TreeSearch::StringSearchIndices TreeSearch::_substring_bounds(const String &p_se return result; } -void TreeSearch::_select_item(TreeItem *item) { - if (!item) +void TreeSearch::_select_item(TreeItem *p_item) { + if (!p_item) return; - tree_reference->set_selected(item, 0); - tree_reference->scroll_to_item(item); - item = item->get_parent(); - while (item) { - item->set_collapsed(false); - item = item->get_parent(); + tree_reference->set_selected(p_item, 0); + + // first unfold ancestors + TreeItem * ancestor = p_item->get_parent(); + ancestor = ancestor->get_parent(); + while (ancestor) { + ancestor->set_collapsed(false); + ancestor = ancestor->get_parent(); } + // then scroll to [item] + tree_reference->scroll_to_item(p_item); + } void TreeSearch::_select_first_match() { @@ -477,9 +481,6 @@ TreeSearch::TreeSearch() { search_panel = memnew(TreeSearchPanel); } -TreeSearch::~TreeSearch() { -} - /* !TreeSearch */ #endif // TOOLS_ENABLED \ No newline at end of file diff --git a/editor/tree_search.h b/editor/tree_search.h index 8f91dd2..0d4b5a2 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -52,8 +52,8 @@ private: void _initialize_close_callbacks(); void _add_spacer(float width_multiplier = 1.f); - void _on_draw_highlight(TreeItem *item, Rect2 rect); - void _emit_text_changed(const String &text); + void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect); + void _emit_text_changed(const String &p_text); void _emit_text_submitted(const String &p_text); void _emit_filter_toggled(); void _notification(int p_what); @@ -91,16 +91,16 @@ private: void _filter_tree(const String &p_search_mask); void _highlight_tree(const String &p_search_mask); - void _draw_highlight_item(TreeItem *tree_item, Rect2 rect, String search_mask, Callable parent_draw_method); - void _update_matching_entries(const String &search_mask); - void _update_ordered_tree_items(TreeItem *tree_item); + void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, String p_search_mask, Callable p_parent_draw_method); + void _update_matching_entries(const String &p_search_mask); + void _update_ordered_tree_items(TreeItem *p_tree_item); void _update_number_matches(); - Vector _find_matching_entries(TreeItem *tree_item, const String &search_mask, Vector &buffer); - StringSearchIndices _substring_bounds(const String &searchable, const String &search_mask) const; + Vector _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_buffer); + StringSearchIndices _substring_bounds(const String &p_searchable, const String &p_search_mask) const; - void _select_item(TreeItem *item); + void _select_item(TreeItem *p_item); void _select_first_match(); void _select_next_match(); @@ -111,12 +111,11 @@ protected: public: // we will add everything from TaskTree.h - void update_search(Tree *tree); - void on_item_edited(TreeItem *item); + void update_search(Tree *p_tree); + void on_item_edited(TreeItem *p_item); TreeSearchPanel *search_panel; TreeSearch(); - ~TreeSearch(); }; #endif // TREE_SEARCH_H From af15dde4c896b76db66689d49ced72730b8e7775 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Thu, 26 Sep 2024 05:37:19 +0000 Subject: [PATCH 11/45] Simplify signals of TreeSearchPanel --- editor/task_tree.cpp | 4 +--- editor/tree_search.cpp | 15 +++++---------- editor/tree_search.h | 3 +-- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index bdeea48..b71468d 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -533,10 +533,8 @@ void TaskTree::_notification(int p_what) { tree->connect("multi_selected", callable_mp(this, &TaskTree::_on_item_selected).unbind(3), CONNECT_DEFERRED); tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_activated)); tree->connect("item_collapsed", callable_mp(this, &TaskTree::_on_item_collapsed)); - // TODO: Simplify these signals into one (candidate names: changed, updated, update_requested): - tree_search->search_panel->connect("text_changed", callable_mp(this, &TaskTree::_update_tree).unbind(1)); + tree_search->search_panel->connect("update_requested", callable_mp(this, &TaskTree::_update_tree)); tree_search->search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); - tree_search->search_panel->connect("filter_toggled", callable_mp(this, &TaskTree::_update_tree)); } break; case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index d1b0bdf..ee6d716 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -82,35 +82,30 @@ void TreeSearchPanel::_add_spacer(float p_width_multiplier) { add_child(spacer); } -void TreeSearchPanel::_emit_text_changed(const String &p_text) { - this->emit_signal("text_changed", p_text); -} - void TreeSearchPanel::_emit_text_submitted(const String &p_text) { this->emit_signal("text_submitted"); } -void TreeSearchPanel::_emit_filter_toggled() { - this->emit_signal("filter_toggled"); +void TreeSearchPanel::_emit_update_requested(){ + emit_signal("update_requested"); } void TreeSearchPanel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { _initialize_controls(); - line_edit_search->connect("text_changed", callable_mp(this, &TreeSearchPanel::_emit_text_changed)); + line_edit_search->connect("text_changed", callable_mp(this, &TreeSearchPanel::_emit_update_requested).unbind(1)); _initialize_close_callbacks(); line_edit_search->connect("text_submitted", callable_mp(this, &TreeSearchPanel::_emit_text_submitted)); - check_button_filter_highlight->connect("pressed", callable_mp(this, &TreeSearchPanel::_emit_filter_toggled)); + check_button_filter_highlight->connect("pressed", callable_mp(this, &TreeSearchPanel::_emit_update_requested)); break; } } } void TreeSearchPanel::_bind_methods() { - ADD_SIGNAL(MethodInfo("text_changed")); + ADD_SIGNAL(MethodInfo("update_requested")); ADD_SIGNAL(MethodInfo("text_submitted")); - ADD_SIGNAL(MethodInfo("filter_toggled")); } TreeSearchPanel::TreeSearchPanel() { diff --git a/editor/tree_search.h b/editor/tree_search.h index 0d4b5a2..dfd59ce 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -53,9 +53,8 @@ private: void _add_spacer(float width_multiplier = 1.f); void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect); - void _emit_text_changed(const String &p_text); void _emit_text_submitted(const String &p_text); - void _emit_filter_toggled(); + void _emit_update_requested(); void _notification(int p_what); protected: From 0fc11bf05b4d23279948888b21d8a06377fc7855 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Thu, 26 Sep 2024 15:36:59 +0000 Subject: [PATCH 12/45] Make TreeSearch::_select_item consistent --- editor/tree_search.cpp | 20 +++++++++++++------- editor/tree_search.h | 8 +++----- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index ee6d716..1bce795 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -35,6 +35,7 @@ #include #include #include + #endif // LIMBOAI_GDEXTENSION #define UPPER_BOUND (1 << 15) // for substring search. @@ -58,14 +59,16 @@ void TreeSearchPanel::_initialize_controls() { this->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); this->set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically - // hack add separator to the left so line edit doesn't clip. line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); _add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. add_child(line_edit_search); + _add_spacer(0.25); + add_child(check_button_filter_highlight); add_child(label_filter); - _add_spacer(); + + _add_spacer(0.25); add_child(close_button); _add_spacer(0.25); } @@ -86,7 +89,7 @@ void TreeSearchPanel::_emit_text_submitted(const String &p_text) { this->emit_signal("text_submitted"); } -void TreeSearchPanel::_emit_update_requested(){ +void TreeSearchPanel::_emit_update_requested() { emit_signal("update_requested"); } @@ -364,18 +367,21 @@ TreeSearch::StringSearchIndices TreeSearch::_substring_bounds(const String &p_se void TreeSearch::_select_item(TreeItem *p_item) { if (!p_item) return; - tree_reference->set_selected(p_item, 0); - + // first unfold ancestors - TreeItem * ancestor = p_item->get_parent(); + TreeItem *ancestor = p_item->get_parent(); ancestor = ancestor->get_parent(); while (ancestor) { ancestor->set_collapsed(false); ancestor = ancestor->get_parent(); } - // then scroll to [item] + + //then scroll to [item] tree_reference->scroll_to_item(p_item); + // ...and select it + tree_reference->deselect_all(); + tree_reference->set_selected(p_item, 0); } void TreeSearch::_select_first_match() { diff --git a/editor/tree_search.h b/editor/tree_search.h index dfd59ce..3980070 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -95,7 +95,6 @@ private: void _update_ordered_tree_items(TreeItem *p_tree_item); void _update_number_matches(); - Vector _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_buffer); StringSearchIndices _substring_bounds(const String &p_searchable, const String &p_search_mask) const; @@ -103,8 +102,9 @@ private: void _select_first_match(); void _select_next_match(); - template - bool _vector_has_bsearch(Vector p_vec, T* element); + template + bool _vector_has_bsearch(Vector p_vec, T *element); + protected: static void _bind_methods() {} @@ -119,5 +119,3 @@ public: #endif // TREE_SEARCH_H #endif // ! TOOLS_ENABLED - - From 47706e9480b6fc831e171f9a6c39ca0d7b6d988c Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Fri, 27 Sep 2024 15:02:21 +0000 Subject: [PATCH 13/45] Make TreeSearch::search_panel private and fix nulltpr --- editor/task_tree.cpp | 13 ++++++++----- editor/task_tree.h | 3 ++- editor/tree_search.cpp | 8 ++++---- editor/tree_search.h | 8 +++++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index b71468d..5e56271 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -533,14 +533,15 @@ void TaskTree::_notification(int p_what) { tree->connect("multi_selected", callable_mp(this, &TaskTree::_on_item_selected).unbind(3), CONNECT_DEFERRED); tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_activated)); tree->connect("item_collapsed", callable_mp(this, &TaskTree::_on_item_collapsed)); - tree_search->search_panel->connect("update_requested", callable_mp(this, &TaskTree::_update_tree)); - tree_search->search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); + tree_search_panel->connect("update_requested", callable_mp(this, &TaskTree::_update_tree)); + tree_search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); } break; case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); _update_tree(); } break; } + } void TaskTree::_bind_methods() { @@ -568,7 +569,8 @@ void TaskTree::_bind_methods() { } void TaskTree::tree_search_show_and_focus() { - tree_search->search_panel->show_and_focus(); + ERR_FAIL_NULL(tree_search); + tree_search_panel->show_and_focus(); } TaskTree::TaskTree() { @@ -595,8 +597,9 @@ TaskTree::TaskTree() { tree->set_drag_forwarding(callable_mp(this, &TaskTree::_get_drag_data_fw), callable_mp(this, &TaskTree::_can_drop_data_fw), callable_mp(this, &TaskTree::_drop_data_fw)); - tree_search.instantiate(); - vbox_container->add_child(tree_search->search_panel); + tree_search_panel = memnew(TreeSearchPanel); + tree_search = Ref(memnew(TreeSearch(tree_search_panel))); + vbox_container->add_child(tree_search_panel); } TaskTree::~TaskTree() { diff --git a/editor/task_tree.h b/editor/task_tree.h index 30564c6..2373e7f 100644 --- a/editor/task_tree.h +++ b/editor/task_tree.h @@ -46,7 +46,9 @@ private: bool editable; bool updating_tree; HashMap probability_rect_cache; + Ref tree_search; + TreeSearchPanel *tree_search_panel; struct ThemeCache { Ref comment_font; @@ -109,7 +111,6 @@ public: bool selected_has_probability() const; virtual bool editor_can_reload_from_file() { return false; } - TaskTree(); ~TaskTree(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 1bce795..4916ad7 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -367,15 +367,14 @@ TreeSearch::StringSearchIndices TreeSearch::_substring_bounds(const String &p_se void TreeSearch::_select_item(TreeItem *p_item) { if (!p_item) return; + ERR_FAIL_COND(!tree_reference || p_item->get_tree() != tree_reference); // first unfold ancestors TreeItem *ancestor = p_item->get_parent(); - ancestor = ancestor->get_parent(); while (ancestor) { ancestor->set_collapsed(false); ancestor = ancestor->get_parent(); } - //then scroll to [item] tree_reference->scroll_to_item(p_item); @@ -476,10 +475,11 @@ void TreeSearch::update_search(Tree *p_tree) { if (search_mode == TreeSearchMode::FILTER) { _filter_tree(search_mask); } + } -TreeSearch::TreeSearch() { - search_panel = memnew(TreeSearchPanel); +TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { + search_panel = p_search_panel; } /* !TreeSearch */ diff --git a/editor/tree_search.h b/editor/tree_search.h index 3980070..24ceae2 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -81,8 +81,10 @@ private: } }; - Tree *tree_reference; + TreeSearchPanel *search_panel; + // These variables are updated every time the update_search method is called. + Tree *tree_reference; Vector ordered_tree_items; Vector matching_entries; HashMap number_matches; @@ -112,9 +114,9 @@ public: // we will add everything from TaskTree.h void update_search(Tree *p_tree); void on_item_edited(TreeItem *p_item); - TreeSearchPanel *search_panel; - TreeSearch(); + TreeSearch(){ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly");} + TreeSearch(TreeSearchPanel * p_search_panel); }; #endif // TREE_SEARCH_H From 9a1641e8ab608ce4d0c94ce7dd108ba128351eed Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Fri, 27 Sep 2024 15:24:14 +0000 Subject: [PATCH 14/45] Move TreeSearch::update_tree in TaskTree from update_tree Catches all cases where the tree is modified. Also those, where for example the tab is switched. --- editor/task_tree.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 5e56271..31f0fe8 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -47,6 +47,12 @@ TreeItem *TaskTree::_create_tree(const Ref &p_task, TreeItem *p_parent, _create_tree(p_task->get_child(i), item); } _update_item(item); + + // update TreeSearch if root task was created + if (tree->get_root() == item){ + tree_search->update_search(tree); + } + return item; } @@ -126,7 +132,6 @@ void TaskTree::_update_tree() { for (const Ref &task : selection) { add_selection(task); } - tree_search->update_search(tree); } TreeItem *TaskTree::_find_item(const Ref &p_task) const { From ffe344d16651307c7c44e1f29897c666a516006d Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Fri, 27 Sep 2024 16:07:42 +0000 Subject: [PATCH 15/45] Make TreeSearch independent, remove unnecessary draw bind --- editor/tree_search.cpp | 17 +++++++++++------ editor/tree_search.h | 15 +++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 4916ad7..3524065 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -13,7 +13,6 @@ #include "tree_search.h" -#include "../bt/behavior_tree.h" #include "../util/limbo_compat.h" // for edscale #include "../util/limbo_string_names.h" #include "../util/limbo_utility.h" @@ -179,7 +178,7 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) { parent_draw_method = entry->get_custom_draw_callback(0); } - Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(p_search_mask, parent_draw_method); + Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(parent_draw_method); // -- this is necessary because of the modularity of this implementation // cache render properties of entry @@ -199,10 +198,11 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) { } } -// custom draw callback for highlighting (bind 2) -void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, String p_search_mask, Callable p_parent_draw_method) { - if (!p_tree_item) +// custom draw callback for highlighting (bind the parent_drw_method to this) +void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) { + if (!p_tree_item){ return; + } // call any parent draw methods such as for probability FIRST. p_parent_draw_method.call(p_tree_item, p_rect); @@ -221,7 +221,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Strin // substring size String string_full = p_tree_item->get_text(0); - StringSearchIndices substring_idx = _substring_bounds(string_full, p_search_mask); + StringSearchIndices substring_idx = _substring_bounds(string_full, _get_search_mask()); String substring_match = string_full.substr(substring_idx.lower, substring_idx.upper - substring_idx.lower); Vector2 substring_match_size = font->get_string_size(substring_match, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); @@ -310,6 +310,11 @@ void TreeSearch::_update_number_matches() { } } +String TreeSearch::_get_search_mask() { + ERR_FAIL_COND_V(!search_panel, ""); + return search_panel->get_text(); +} + Vector TreeSearch::_find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) { if (!p_tree_item) return p_accum; diff --git a/editor/tree_search.h b/editor/tree_search.h index 24ceae2..830c36c 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -14,15 +14,13 @@ #ifndef TREE_SEARCH_H #define TREE_SEARCH_H -#include "../bt/tasks/bt_task.h" // for tree item parsing - #ifdef LIMBOAI_GDEXTENSION #include #include #include #include #include - +#include #endif // LIMBOAI_GDEXTENSION #ifdef LIMBOAI_MODULE @@ -31,6 +29,7 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/tree.h" +#include "core/templates/hash_map.h" #endif // LIMBOAI_MODULE using namespace godot; @@ -83,21 +82,26 @@ private: TreeSearchPanel *search_panel; - // These variables are updated every time the update_search method is called. + // For TaskTree: These are updated when the tree is updated through TaskTree::_create_tree. Tree *tree_reference; Vector ordered_tree_items; Vector matching_entries; HashMap number_matches; HashMap callable_cache; + // Update_search() calls these void _filter_tree(const String &p_search_mask); void _highlight_tree(const String &p_search_mask); - void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, String p_search_mask, Callable p_parent_draw_method); + + // Custom draw-Callback (bind inherited Callable). + void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method); + void _update_matching_entries(const String &p_search_mask); void _update_ordered_tree_items(TreeItem *p_tree_item); void _update_number_matches(); Vector _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_buffer); + String _get_search_mask(); StringSearchIndices _substring_bounds(const String &p_searchable, const String &p_search_mask) const; void _select_item(TreeItem *p_item); @@ -111,7 +115,6 @@ protected: static void _bind_methods() {} public: - // we will add everything from TaskTree.h void update_search(Tree *p_tree); void on_item_edited(TreeItem *p_item); From 380f80c2b3166548f01743bd96e21e2ef418135d Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Fri, 27 Sep 2024 16:31:38 +0000 Subject: [PATCH 16/45] Fix Editor scaling for TreeSearch --- editor/tree_search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 3524065..81be88d 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -241,8 +241,8 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla Rect2 draw_rect = p_rect; Vector2 rect_offset = Vector2(substring_before_size.x, 0); - rect_offset.x += p_tree_item->get_icon_max_width(0) * EDSCALE; - rect_offset.x += (h_sep + 4.) * EDSCALE; // TODO: Find better way to determine texts x-offset + rect_offset.x += p_tree_item->get_icon_max_width(0); + rect_offset.x += (h_sep + 4. * EDSCALE); // TODO: Find better way to determine texts x-offset rect_offset.y = (p_rect.size.y - substring_match_size.y) / 2; // center box vertically draw_rect.position += rect_offset - PADDING / 2; @@ -252,7 +252,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla stylebox->draw(p_tree_item->get_tree()->get_canvas_item(), draw_rect); } - // second part: draw number (TODO: maybe use columns) + // second part: draw number int num_mat = number_matches.has(p_tree_item) ? number_matches.get(p_tree_item) : 0; if (num_mat > 0) { float h_sep = p_tree_item->get_tree()->get_theme_constant("h_separation"); From cc8f099d82f9aeaa3c3affd2fb3bf588834bdd5e Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 12:08:27 +0000 Subject: [PATCH 17/45] Improve TreeSearch performance; part1 --- editor/tree_search.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 81be88d..ec19741 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -320,12 +320,18 @@ Vector TreeSearch::_find_matching_entries(TreeItem *p_tree_item, con return p_accum; StringSearchIndices item_search_indices = _substring_bounds(p_tree_item->get_text(0), p_search_mask); if (item_search_indices.hit()) - p_accum.insert(p_accum.bsearch(p_tree_item, true), p_tree_item); + p_accum.push_back(p_tree_item); for (int i = 0; i < p_tree_item->get_child_count(); i++) { TreeItem *child = p_tree_item->get_child(i); _find_matching_entries(child, p_search_mask, p_accum); } + + // sort the result if we are at the root + if (p_tree_item == p_tree_item->get_tree()->get_root()) { + p_accum.sort(); + } + return p_accum; } From 84b2a605211feed87b6d9f9022bab13af02c2edc Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 17:29:45 +0200 Subject: [PATCH 18/45] Adjust tooltips + misc-menu entry --- editor/limbo_ai_editor_plugin.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 4300b4d..57becb6 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -457,7 +457,7 @@ void LimboAIEditor::_process_shortcut_input(const Ref &p_event) { _on_save_pressed(); } else if (LW_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) { _popup_file_dialog(load_dialog); - } else if (LW_IS_SHORTCUT("limbo_ai/search_tree", p_event)) { + } else if (LW_IS_SHORTCUT("limbo_ai/find_task", p_event)) { task_tree->tree_search_show_and_focus(); } else { handled = false; @@ -1326,7 +1326,7 @@ void LimboAIEditor::_update_misc_menu() { MISC_CREATE_SCRIPT_TEMPLATE); misc_menu->add_separator(); - misc_menu->add_icon_item(theme_cache.search_icon, ("Search Tree"), MISC_SEARCH_TREE); + misc_menu->add_icon_shortcut(theme_cache.search_icon, LW_GET_SHORTCUT("limbo_ai/find_task"), MISC_SEARCH_TREE); } void LimboAIEditor::_update_banners() { @@ -1522,8 +1522,7 @@ LimboAIEditor::LimboAIEditor() { LW_SHORTCUT("limbo_ai/jump_to_owner", TTR("Jump to Owner"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(J))); LW_SHORTCUT("limbo_ai/close_tab", TTR("Close Tab"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(W))); LW_SHORTCUT("limbo_ai/find_task", TTR("Find Task"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F))); - LW_SHORTCUT("limbo_ai/hide_tree_search", TTR("Hide BehaviorTrees Search Panel"), (Key)(LW_KEY(ESCAPE))); - LW_SHORTCUT("limbo_ai/search_tree", TTR("Shows the BehaviorTree Search Panel"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F))); + LW_SHORTCUT("limbo_ai/hide_tree_search", TTR("Close Search"), (Key)(LW_KEY(ESCAPE))); set_process_shortcut_input(true); From 329e90dfc66e12a0323418cf9ea4d7471eabcc44 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 15:45:52 +0000 Subject: [PATCH 19/45] Rename TreeSearch::notify_item_edited Also: Run clang-format. Remove comment. --- editor/task_tree.cpp | 4 +--- editor/tree_search.cpp | 7 +++---- editor/tree_search.h | 11 ++++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 31f0fe8..7a4c5bc 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -112,7 +112,7 @@ void TaskTree::_update_item(TreeItem *p_item) { if (!warning_text.is_empty()) { p_item->add_button(0, theme_cache.task_warning_icon, 0, false, warning_text); } - tree_search->on_item_edited(p_item); // this is necessary to preserve custom drawing from tree search. + tree_search->notify_item_edited(p_item); // this is necessary to preserve custom drawing from tree search. } void TaskTree::_update_tree() { @@ -582,8 +582,6 @@ TaskTree::TaskTree() { editable = true; updating_tree = false; - // for Tree + TreeSearch, we want a VBoxContainer. For now, rather than changing this classes type, let's do nesting: - // TaskTree -> VBoxContainer -> [Tree, TreeSearchPanel] VBoxContainer *vbox_container = memnew(VBoxContainer); add_child(vbox_container); vbox_container->set_anchors_preset(PRESET_FULL_RECT); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index ec19741..0711272 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -200,7 +200,7 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) { // custom draw callback for highlighting (bind the parent_drw_method to this) void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) { - if (!p_tree_item){ + if (!p_tree_item) { return; } // call any parent draw methods such as for probability FIRST. @@ -326,7 +326,7 @@ Vector TreeSearch::_find_matching_entries(TreeItem *p_tree_item, con TreeItem *child = p_tree_item->get_child(i); _find_matching_entries(child, p_search_mask, p_accum); } - + // sort the result if we are at the root if (p_tree_item == p_tree_item->get_tree()->get_root()) { p_accum.sort(); @@ -448,7 +448,7 @@ inline bool TreeSearch::_vector_has_bsearch(Vector p_vec, T *element) { return in_array && p_vec[idx] == element; } -void TreeSearch::on_item_edited(TreeItem *item) { +void TreeSearch::notify_item_edited(TreeItem *item) { if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) { return; } @@ -486,7 +486,6 @@ void TreeSearch::update_search(Tree *p_tree) { if (search_mode == TreeSearchMode::FILTER) { _filter_tree(search_mask); } - } TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { diff --git a/editor/tree_search.h b/editor/tree_search.h index 830c36c..a1c8259 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -24,12 +24,12 @@ #endif // LIMBOAI_GDEXTENSION #ifdef LIMBOAI_MODULE +#include "core/templates/hash_map.h" #include "scene/gui/check_box.h" #include "scene/gui/flow_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/tree.h" -#include "core/templates/hash_map.h" #endif // LIMBOAI_MODULE using namespace godot; @@ -41,6 +41,7 @@ enum TreeSearchMode { class TreeSearchPanel : public HFlowContainer { GDCLASS(TreeSearchPanel, HFlowContainer) + private: Button *toggle_button_filter_highlight; Button *close_button; @@ -95,7 +96,7 @@ private: // Custom draw-Callback (bind inherited Callable). void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method); - + void _update_matching_entries(const String &p_search_mask); void _update_ordered_tree_items(TreeItem *p_tree_item); void _update_number_matches(); @@ -116,10 +117,10 @@ protected: public: void update_search(Tree *p_tree); - void on_item_edited(TreeItem *p_item); + void notify_item_edited(TreeItem *p_item); - TreeSearch(){ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly");} - TreeSearch(TreeSearchPanel * p_search_panel); + TreeSearch() { ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly"); } + TreeSearch(TreeSearchPanel *p_search_panel); }; #endif // TREE_SEARCH_H From 3b73f24f33566499683f2baa2e42d9aec040d7da Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 17:58:42 +0200 Subject: [PATCH 20/45] Make TreeSearchMode member of TreeSearch --- editor/tree_search.cpp | 6 ++-- editor/tree_search.h | 68 ++++++++++++++++++++++-------------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 0711272..7941a15 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -118,11 +118,11 @@ bool TreeSearchPanel::has_focus() { return false; } -TreeSearchMode TreeSearchPanel::get_search_mode() { +TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { - return TreeSearchMode::HIGHLIGHT; + return TreeSearch::TreeSearchMode::HIGHLIGHT; } - return TreeSearchMode::FILTER; + return TreeSearch::TreeSearchMode::FILTER; } String TreeSearchPanel::get_text() { diff --git a/editor/tree_search.h b/editor/tree_search.h index a1c8259..0d1ac71 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -34,39 +34,7 @@ using namespace godot; -enum TreeSearchMode { - HIGHLIGHT = 0, - FILTER = 1 -}; - -class TreeSearchPanel : public HFlowContainer { - GDCLASS(TreeSearchPanel, HFlowContainer) - -private: - Button *toggle_button_filter_highlight; - Button *close_button; - Label *label_filter; - LineEdit *line_edit_search; - CheckBox *check_button_filter_highlight; - void _initialize_controls(); - void _initialize_close_callbacks(); - void _add_spacer(float width_multiplier = 1.f); - - void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect); - void _emit_text_submitted(const String &p_text); - void _emit_update_requested(); - void _notification(int p_what); - -protected: - static void _bind_methods(); - -public: - TreeSearchMode get_search_mode(); - String get_text(); - void show_and_focus(); - TreeSearchPanel(); - bool has_focus(); -}; +class TreeSearchPanel; class TreeSearch : public RefCounted { GDCLASS(TreeSearch, RefCounted) @@ -116,6 +84,11 @@ protected: static void _bind_methods() {} public: + enum TreeSearchMode { + HIGHLIGHT = 0, + FILTER = 1 + }; + void update_search(Tree *p_tree); void notify_item_edited(TreeItem *p_item); @@ -123,5 +96,34 @@ public: TreeSearch(TreeSearchPanel *p_search_panel); }; +class TreeSearchPanel : public HFlowContainer { + GDCLASS(TreeSearchPanel, HFlowContainer) + +private: + Button *toggle_button_filter_highlight; + Button *close_button; + Label *label_filter; + LineEdit *line_edit_search; + CheckBox *check_button_filter_highlight; + void _initialize_controls(); + void _initialize_close_callbacks(); + void _add_spacer(float width_multiplier = 1.f); + + void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect); + void _emit_text_submitted(const String &p_text); + void _emit_update_requested(); + void _notification(int p_what); + +protected: + static void _bind_methods(); + +public: + TreeSearch::TreeSearchMode get_search_mode(); + String get_text(); + void show_and_focus(); + TreeSearchPanel(); + bool has_focus(); +}; + #endif // TREE_SEARCH_H #endif // ! TOOLS_ENABLED From cd85e6dd30b199f3320807ffc16e4a5d30adfa3e Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 16:45:11 +0000 Subject: [PATCH 21/45] Initialize controls in constructor, bind emit callbacks without wrapper in TreeSearchPanel --- editor/tree_search.cpp | 49 +++++++++++++++++------------------------- editor/tree_search.h | 4 ---- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 7941a15..fa9fb1a 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -51,12 +51,11 @@ void TreeSearchPanel::_initialize_controls() { label_filter->set_text(TTR("Filter")); - BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); close_button->set_theme_type_variation("FlatButton"); // positioning and sizing - this->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); - this->set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically + set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); + set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); @@ -72,34 +71,28 @@ void TreeSearchPanel::_initialize_controls() { _add_spacer(0.25); } -void TreeSearchPanel::_initialize_close_callbacks() { - Callable calleable_set_invisible = Callable(this, "set_visible").bind(false); // don't need a custom bind. - close_button->connect("pressed", calleable_set_invisible); - close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); -} - void TreeSearchPanel::_add_spacer(float p_width_multiplier) { Control *spacer = memnew(Control); spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * p_width_multiplier, 0.0)); add_child(spacer); } -void TreeSearchPanel::_emit_text_submitted(const String &p_text) { - this->emit_signal("text_submitted"); -} - -void TreeSearchPanel::_emit_update_requested() { - emit_signal("update_requested"); -} - void TreeSearchPanel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - _initialize_controls(); - line_edit_search->connect("text_changed", callable_mp(this, &TreeSearchPanel::_emit_update_requested).unbind(1)); - _initialize_close_callbacks(); - line_edit_search->connect("text_submitted", callable_mp(this, &TreeSearchPanel::_emit_text_submitted)); - check_button_filter_highlight->connect("pressed", callable_mp(this, &TreeSearchPanel::_emit_update_requested)); + BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); + + // close callbacks + close_button->connect("pressed", Callable(this, "set_visible").bind(false)); + close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); + + // search callbacks + Callable c_update_requested = Callable(this, "emit_signal").bind("update_requested"); + Callable c_text_submitted = Callable((Object *)this, "emit_signal").bind("text_submitted"); + + line_edit_search->connect("text_changed", c_update_requested.unbind(1)); + check_button_filter_highlight->connect("pressed", c_update_requested); + line_edit_search->connect("text_submitted", c_text_submitted.unbind(1)); break; } } @@ -111,11 +104,8 @@ void TreeSearchPanel::_bind_methods() { } TreeSearchPanel::TreeSearchPanel() { - this->set_visible(false); -} - -bool TreeSearchPanel::has_focus() { - return false; + _initialize_controls(); + set_visible(false); } TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { @@ -126,13 +116,14 @@ TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { } String TreeSearchPanel::get_text() { - if (!line_edit_search) + if (!line_edit_search) { return String(); + } return line_edit_search->get_text(); } void TreeSearchPanel::show_and_focus() { - this->set_visible(true); + set_visible(true); line_edit_search->grab_focus(); } diff --git a/editor/tree_search.h b/editor/tree_search.h index 0d1ac71..4b45075 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -106,12 +106,9 @@ private: LineEdit *line_edit_search; CheckBox *check_button_filter_highlight; void _initialize_controls(); - void _initialize_close_callbacks(); void _add_spacer(float width_multiplier = 1.f); void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect); - void _emit_text_submitted(const String &p_text); - void _emit_update_requested(); void _notification(int p_what); protected: @@ -122,7 +119,6 @@ public: String get_text(); void show_and_focus(); TreeSearchPanel(); - bool has_focus(); }; #endif // TREE_SEARCH_H From 62460496e4f03745d7546840c8780a16302cfbd9 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 19:11:59 +0200 Subject: [PATCH 22/45] Address set_text in THEME_CHANGED, address inconsistency --- editor/tree_search.cpp | 21 ++++++++++++--------- editor/tree_search.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index fa9fb1a..b95e335 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -49,8 +49,6 @@ void TreeSearchPanel::_initialize_controls() { line_edit_search->set_placeholder(TTR("Search tree")); - label_filter->set_text(TTR("Filter")); - close_button->set_theme_type_variation("FlatButton"); // positioning and sizing @@ -95,6 +93,9 @@ void TreeSearchPanel::_notification(int p_what) { line_edit_search->connect("text_submitted", c_text_submitted.unbind(1)); break; } + case NOTIFICATION_THEME_CHANGED: { + label_filter->set_text(TTR("Filter")); + } } } @@ -263,7 +264,8 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla void TreeSearch::_update_matching_entries(const String &p_search_mask) { Vector accum; - matching_entries = _find_matching_entries(tree_reference->get_root(), p_search_mask, accum); + _find_matching_entries(tree_reference->get_root(), p_search_mask, accum); + matching_entries = accum; } /* this linearizes the tree into [ordered_tree_items] like so: @@ -306,13 +308,14 @@ String TreeSearch::_get_search_mask() { return search_panel->get_text(); } -Vector TreeSearch::_find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) { - if (!p_tree_item) - return p_accum; +void TreeSearch::_find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) { + if (!p_tree_item) { + return; + } StringSearchIndices item_search_indices = _substring_bounds(p_tree_item->get_text(0), p_search_mask); - if (item_search_indices.hit()) + if (item_search_indices.hit()) { p_accum.push_back(p_tree_item); - + } for (int i = 0; i < p_tree_item->get_child_count(); i++) { TreeItem *child = p_tree_item->get_child(i); _find_matching_entries(child, p_search_mask, p_accum); @@ -323,7 +326,7 @@ Vector TreeSearch::_find_matching_entries(TreeItem *p_tree_item, con p_accum.sort(); } - return p_accum; + return; } // Returns the lower and upper bounds of a substring. Does fuzzy search: Simply looks if words exist in right ordering. diff --git a/editor/tree_search.h b/editor/tree_search.h index 4b45075..7bd3eb0 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -69,7 +69,7 @@ private: void _update_ordered_tree_items(TreeItem *p_tree_item); void _update_number_matches(); - Vector _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_buffer); + void _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum); String _get_search_mask(); StringSearchIndices _substring_bounds(const String &p_searchable, const String &p_search_mask) const; From a31b8b7520920cfcfc0dd20e18d1139ba7c260e1 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 17:31:20 +0000 Subject: [PATCH 23/45] Follow up: Move BUTTON_SETICON to THEME_CHANGED --- editor/tree_search.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index b95e335..d3c9cd3 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -78,7 +78,6 @@ void TreeSearchPanel::_add_spacer(float p_width_multiplier) { void TreeSearchPanel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); // close callbacks close_button->connect("pressed", Callable(this, "set_visible").bind(false)); @@ -94,6 +93,7 @@ void TreeSearchPanel::_notification(int p_what) { break; } case NOTIFICATION_THEME_CHANGED: { + BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); label_filter->set_text(TTR("Filter")); } } @@ -397,7 +397,6 @@ void TreeSearch::_select_first_match() { if (!_vector_has_bsearch(matching_entries, item)) { continue; } - String debug_string = "["; _select_item(item); return; } @@ -422,14 +421,12 @@ void TreeSearch::_select_next_match() { } // find the best fitting entry. - for (int i = 0; i < ordered_tree_items.size(); i++) { + for (int i = MAX(0, selected_idx) + 1; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; - if (!_vector_has_bsearch(matching_entries, item) || selected_idx >= i) { - continue; + if (_vector_has_bsearch(matching_entries, item)) { + _select_item(item); + return; } - - _select_item(item); - return; } _select_first_match(); // wrap around. } @@ -473,9 +470,6 @@ void TreeSearch::update_search(Tree *p_tree) { _update_number_matches(); _highlight_tree(search_mask); - if (!search_panel->is_connected("text_submitted", callable_mp(this, &TreeSearch::_select_next_match))) { - search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); - } if (search_mode == TreeSearchMode::FILTER) { _filter_tree(search_mask); @@ -484,6 +478,7 @@ void TreeSearch::update_search(Tree *p_tree) { TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { search_panel = p_search_panel; + search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); } /* !TreeSearch */ From acb2bcc9016698d84a4aaad6eaf783c0a12f8784 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 17:45:17 +0000 Subject: [PATCH 24/45] Use LW_NAME in TreeSearch where appropriate --- editor/tree_search.cpp | 12 ++++++------ util/limbo_string_names.cpp | 3 +++ util/limbo_string_names.h | 3 +++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index d3c9cd3..d760b47 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -49,7 +49,7 @@ void TreeSearchPanel::_initialize_controls() { line_edit_search->set_placeholder(TTR("Search tree")); - close_button->set_theme_type_variation("FlatButton"); + close_button->set_theme_type_variation(LW_NAME(FlatButton)); // positioning and sizing set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); @@ -222,11 +222,11 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla Vector2 substring_before_size = font->get_string_size(substring_before, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); // stylebox - Ref stylebox = p_tree_item->get_tree()->get_theme_stylebox("Focus"); + Ref stylebox = p_tree_item->get_tree()->get_theme_stylebox(LW_NAME(Focus)); ERR_FAIL_NULL(stylebox); // extract separation - float h_sep = p_tree_item->get_tree()->get_theme_constant("h_separation"); + float h_sep = p_tree_item->get_tree()->get_theme_constant(LW_NAME(h_separation)); // compose draw rect const Vector2 PADDING = Vector2(4., 2.); @@ -247,9 +247,9 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla // second part: draw number int num_mat = number_matches.has(p_tree_item) ? number_matches.get(p_tree_item) : 0; if (num_mat > 0) { - float h_sep = p_tree_item->get_tree()->get_theme_constant("h_separation"); - Ref font = tree_reference->get_theme_font("font"); - float font_size = tree_reference->get_theme_font_size("font") * 0.75; + float h_sep = p_tree_item->get_tree()->get_theme_constant(LW_NAME(h_separation)); + Ref font = tree_reference->get_theme_font(LW_NAME(font)); + float font_size = tree_reference->get_theme_font_size(LW_NAME(font)) * 0.75; String num_string = String::num_int64(num_mat); Vector2 string_size = font->get_string_size(num_string, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size); diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index a5a20c7..1a13566 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -67,6 +67,8 @@ LimboStringNames::LimboStringNames() { exited = SN("exited"); favorite_tasks_changed = SN("favorite_tasks_changed"); Favorites = SN("Favorites"); + FlatButton = SN("FlatButton"); + Focus = SN("Focus"); focus_exited = SN("focus_exited"); font = SN("font"); font_color = SN("font_color"); @@ -78,6 +80,7 @@ LimboStringNames::LimboStringNames() { GuiTreeArrowRight = SN("GuiTreeArrowRight"); HeaderSmall = SN("HeaderSmall"); Help = SN("Help"); + h_separation = SN("h_separation"); icon_max_width = SN("icon_max_width"); class_icon_size = SN("class_icon_size"); id_pressed = SN("id_pressed"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index f11af7d..6e4c0ba 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -83,6 +83,8 @@ public: StringName exited; StringName favorite_tasks_changed; StringName Favorites; + StringName FlatButton; + StringName Focus; StringName focus_exited; StringName font_color; StringName font_size; @@ -94,6 +96,7 @@ public: StringName GuiTreeArrowRight; StringName HeaderSmall; StringName Help; + StringName h_separation; StringName icon_max_width; StringName class_icon_size; StringName id_pressed; From 68514e2e13041a08f249516aa79cb52fd6cfa862 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sat, 28 Sep 2024 18:04:36 +0000 Subject: [PATCH 25/45] Add missing `break` statement - TreeSearch::notification --- editor/tree_search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index d760b47..c044ffb 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -95,6 +95,7 @@ void TreeSearchPanel::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); label_filter->set_text(TTR("Filter")); + break; } } } From c9095824803d546ea3ecdadcaa26d731ed802fe0 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 00:00:17 +0000 Subject: [PATCH 26/45] TreeSearch(Panel): Align ordering .h <-> .cpp --- editor/tree_search.cpp | 183 ++++++++++++++++++++--------------------- editor/tree_search.h | 20 +++-- 2 files changed, 102 insertions(+), 101 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index c044ffb..75992f2 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -39,98 +39,6 @@ #define UPPER_BOUND (1 << 15) // for substring search. -/* ------- TreeSearchPanel ------- */ - -void TreeSearchPanel::_initialize_controls() { - line_edit_search = memnew(LineEdit); - check_button_filter_highlight = memnew(CheckBox); - close_button = memnew(Button); - label_filter = memnew(Label); - - line_edit_search->set_placeholder(TTR("Search tree")); - - close_button->set_theme_type_variation(LW_NAME(FlatButton)); - - // positioning and sizing - set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); - set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically - - line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); - - _add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. - add_child(line_edit_search); - _add_spacer(0.25); - - add_child(check_button_filter_highlight); - add_child(label_filter); - - _add_spacer(0.25); - add_child(close_button); - _add_spacer(0.25); -} - -void TreeSearchPanel::_add_spacer(float p_width_multiplier) { - Control *spacer = memnew(Control); - spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * p_width_multiplier, 0.0)); - add_child(spacer); -} - -void TreeSearchPanel::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { - - // close callbacks - close_button->connect("pressed", Callable(this, "set_visible").bind(false)); - close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); - - // search callbacks - Callable c_update_requested = Callable(this, "emit_signal").bind("update_requested"); - Callable c_text_submitted = Callable((Object *)this, "emit_signal").bind("text_submitted"); - - line_edit_search->connect("text_changed", c_update_requested.unbind(1)); - check_button_filter_highlight->connect("pressed", c_update_requested); - line_edit_search->connect("text_submitted", c_text_submitted.unbind(1)); - break; - } - case NOTIFICATION_THEME_CHANGED: { - BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); - label_filter->set_text(TTR("Filter")); - break; - } - } -} - -void TreeSearchPanel::_bind_methods() { - ADD_SIGNAL(MethodInfo("update_requested")); - ADD_SIGNAL(MethodInfo("text_submitted")); -} - -TreeSearchPanel::TreeSearchPanel() { - _initialize_controls(); - set_visible(false); -} - -TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { - if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { - return TreeSearch::TreeSearchMode::HIGHLIGHT; - } - return TreeSearch::TreeSearchMode::FILTER; -} - -String TreeSearchPanel::get_text() { - if (!line_edit_search) { - return String(); - } - return line_edit_search->get_text(); -} - -void TreeSearchPanel::show_and_focus() { - set_visible(true); - line_edit_search->grab_focus(); -} - -/* !TreeSearchPanel */ - /* ------- TreeSearch ------- */ void TreeSearch::_filter_tree(const String &p_search_mask) { @@ -484,4 +392,95 @@ TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { /* !TreeSearch */ +/* ------- TreeSearchPanel ------- */ + +void TreeSearchPanel::_initialize_controls() { + line_edit_search = memnew(LineEdit); + check_button_filter_highlight = memnew(CheckBox); + close_button = memnew(Button); + label_filter = memnew(Label); + + line_edit_search->set_placeholder(TTR("Search tree")); + + close_button->set_theme_type_variation(LW_NAME(FlatButton)); + + // positioning and sizing + set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); + set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically + + line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); + + _add_spacer(0.25); // otherwise the lineedits expand margin touches the left border. + add_child(line_edit_search); + _add_spacer(0.25); + + add_child(check_button_filter_highlight); + add_child(label_filter); + + _add_spacer(0.25); + add_child(close_button); + _add_spacer(0.25); +} + +void TreeSearchPanel::_add_spacer(float p_width_multiplier) { + Control *spacer = memnew(Control); + spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * p_width_multiplier, 0.0)); + add_child(spacer); +} + +void TreeSearchPanel::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + // close callbacks + close_button->connect("pressed", Callable(this, "set_visible").bind(false)); + close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); + + // search callbacks + Callable c_update_requested = Callable(this, "emit_signal").bind("update_requested"); + Callable c_text_submitted = Callable((Object *)this, "emit_signal").bind("text_submitted"); + + line_edit_search->connect("text_changed", c_update_requested.unbind(1)); + check_button_filter_highlight->connect("pressed", c_update_requested); + line_edit_search->connect("text_submitted", c_text_submitted.unbind(1)); + break; + } + case NOTIFICATION_THEME_CHANGED: { + BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons))); + label_filter->set_text(TTR("Filter")); + break; + } + } +} + +void TreeSearchPanel::_bind_methods() { + ADD_SIGNAL(MethodInfo("update_requested")); + ADD_SIGNAL(MethodInfo("text_submitted")); +} + +TreeSearchPanel::TreeSearchPanel() { + _initialize_controls(); + set_visible(false); +} + +TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { + if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { + return TreeSearch::TreeSearchMode::HIGHLIGHT; + } + return TreeSearch::TreeSearchMode::FILTER; +} + +String TreeSearchPanel::get_text() { + if (!line_edit_search) { + return String(); + } + return line_edit_search->get_text(); +} + +void TreeSearchPanel::show_and_focus() { + set_visible(true); + line_edit_search->grab_focus(); +} + +/* !TreeSearchPanel */ + #endif // TOOLS_ENABLED \ No newline at end of file diff --git a/editor/tree_search.h b/editor/tree_search.h index 7bd3eb0..f2fd772 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -14,15 +14,6 @@ #ifndef TREE_SEARCH_H #define TREE_SEARCH_H -#ifdef LIMBOAI_GDEXTENSION -#include -#include -#include -#include -#include -#include -#endif // LIMBOAI_GDEXTENSION - #ifdef LIMBOAI_MODULE #include "core/templates/hash_map.h" #include "scene/gui/check_box.h" @@ -32,6 +23,15 @@ #include "scene/gui/tree.h" #endif // LIMBOAI_MODULE +#ifdef LIMBOAI_GDEXTENSION +#include +#include +#include +#include +#include +#include +#endif // LIMBOAI_GDEXTENSION + using namespace godot; class TreeSearchPanel; @@ -96,6 +96,8 @@ public: TreeSearch(TreeSearchPanel *p_search_panel); }; +// -------------------------------------------- + class TreeSearchPanel : public HFlowContainer { GDCLASS(TreeSearchPanel, HFlowContainer) From cb163ebc38bd9046fccb39854d2479fde87c65a9 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 00:08:24 +0000 Subject: [PATCH 27/45] Improve readability of TreeSearch --- editor/tree_search.cpp | 6 ++++-- editor/tree_search.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 75992f2..3fafd38 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -205,8 +205,8 @@ void TreeSearch::_update_number_matches() { for (int i = 0; i < matching_entries.size(); i++) { TreeItem *item = matching_entries[i]; while (item) { - int old_num_value = number_matches.has(item) ? number_matches.get(item) : 0; - number_matches[item] = old_num_value + 1; + int previous_match_cnt = number_matches.has(item) ? number_matches.get(item) : 0; + number_matches[item] = previous_match_cnt + 1; item = item->get_parent(); } } @@ -253,8 +253,10 @@ TreeSearch::StringSearchIndices TreeSearch::_substring_bounds(const String &p_se // Determine if the search should be case-insensitive. bool is_case_insensitive = (p_search_mask == p_search_mask.to_lower()); String searchable_processed = is_case_insensitive ? p_searchable.to_lower() : p_searchable; + PackedStringArray words = p_search_mask.split(" "); int word_position = 0; + for (const String &word : words) { if (word.is_empty()) { continue; // Skip empty words. diff --git a/editor/tree_search.h b/editor/tree_search.h index f2fd772..3c1174e 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -92,7 +92,7 @@ public: void update_search(Tree *p_tree); void notify_item_edited(TreeItem *p_item); - TreeSearch() { ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly"); } + TreeSearch() { ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly."); } TreeSearch(TreeSearchPanel *p_search_panel); }; From e68e0236e97f5f04be9e0aadf77b5041c07a9db0 Mon Sep 17 00:00:00 2001 From: monxa <84077629+monxa@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:50:07 +0200 Subject: [PATCH 28/45] Improve performance for TreeSearch (#1) * Improve TreeSearch performance. Experimental, hence this is on a different branch. This commit vastly improves performance by not updating the tree for search mask changes. Relates to: https://github.com/limbonaut/limboai/pull/229 * Fix SearchTree overdraw after performance optimization * Manage Performance optimizations: TreeSearch no. 2 - Carefully manage callable_cache - Only clear filter when previously filtered - Reintroduce sorting for ordered_tree_items This commit addresses performance issues in TreeSearch and fixes a critical bug where ordered_tree_items was not being sorted. The bug was introduced during a merge with the main feature branch. * Use queue_redraw as much as possible for Tree updates. * Fix TreeSearch after performance considerations --- editor/task_tree.cpp | 4 +- editor/tree_search.cpp | 131 +++++++++++++++++++++++++++-------------- editor/tree_search.h | 11 +++- 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index 7a4c5bc..a976ca0 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -538,8 +538,8 @@ void TaskTree::_notification(int p_what) { tree->connect("multi_selected", callable_mp(this, &TaskTree::_on_item_selected).unbind(3), CONNECT_DEFERRED); tree->connect("item_activated", callable_mp(this, &TaskTree::_on_item_activated)); tree->connect("item_collapsed", callable_mp(this, &TaskTree::_on_item_collapsed)); - tree_search_panel->connect("update_requested", callable_mp(this, &TaskTree::_update_tree)); - tree_search_panel->connect("visibility_changed", callable_mp(this, &TaskTree::_update_tree)); + tree_search_panel->connect("update_requested", callable_mp(tree_search.ptr(), &TreeSearch::update_search).bind(tree)); + tree_search_panel->connect("visibility_changed", callable_mp(tree_search.ptr(), &TreeSearch::update_search).bind(tree)); } break; case NOTIFICATION_THEME_CHANGED: { _do_update_theme_item_cache(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 3fafd38..e0494d1 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -41,6 +41,20 @@ /* ------- TreeSearch ------- */ +void TreeSearch::_clean_callable_cache() { + ERR_FAIL_COND(!tree_reference); + HashMap new_callable_cache; + new_callable_cache.reserve(callable_cache.size()); // Efficiency + + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *cur_item = ordered_tree_items[i]; + if (callable_cache.has(cur_item)) { + new_callable_cache[cur_item] = callable_cache[cur_item]; + } + } + callable_cache = new_callable_cache; +} + void TreeSearch::_filter_tree(const String &p_search_mask) { if (matching_entries.size() == 0) { return; @@ -63,42 +77,66 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { } } -void TreeSearch::_highlight_tree(const String &p_search_mask) { - callable_cache.clear(); - for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *entry = ordered_tree_items[i]; +// makes all tree items visible. +void TreeSearch::_clear_filter() { + ERR_FAIL_COND(!tree_reference); + Vector items = { tree_reference->get_root() }; + for (int idx = 0; idx < items.size(); idx++) { + TreeItem *cur_item = items[idx]; + cur_item->set_visible(true); - int num_m = number_matches.has(entry) ? number_matches.get(entry) : 0; - if (num_m == 0) { - continue; + for (int i = 0; i < cur_item->get_child_count(); i++) { + items.push_back(cur_item->get_child(i)); } - - // make sure to also call any draw method already defined. - Callable parent_draw_method; - if (entry->get_cell_mode(0) == TreeItem::CELL_MODE_CUSTOM) { - parent_draw_method = entry->get_custom_draw_callback(0); - } - - Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(parent_draw_method); - - // -- this is necessary because of the modularity of this implementation - // cache render properties of entry - String cached_text = entry->get_text(0); - Ref cached_icon = entry->get_icon(0); - int cached_max_width = entry->get_icon_max_width(0); - callable_cache[entry] = draw_callback; - - // this removes render properties in entry - entry->set_custom_draw_callback(0, draw_callback); - entry->set_cell_mode(0, TreeItem::CELL_MODE_CUSTOM); - - // restore render properties - entry->set_text(0, cached_text); - entry->set_icon(0, cached_icon); - entry->set_icon_max_width(0, cached_max_width); } } +void TreeSearch::_highlight_tree() { + ERR_FAIL_COND(!tree_reference); + for (int i = 0; i < matching_entries.size(); i++) { + TreeItem *tree_item = matching_entries[i]; + _highlight_tree_item(tree_item); + } + tree_reference->queue_redraw(); +} + +void TreeSearch::_highlight_tree_item(TreeItem *p_tree_item) { + int num_m = number_matches.has(p_tree_item) ? number_matches.get(p_tree_item) : 0; + + if (num_m == 0) { + return; + } + + // make sure to also call any draw method already defined. + Callable parent_draw_method; + if (p_tree_item->get_cell_mode(0) == TreeItem::CELL_MODE_CUSTOM) { + parent_draw_method = p_tree_item->get_custom_draw_callback(0); + } + + // if the cached draw method is already applied, do nothing. + if (callable_cache.has(p_tree_item) && parent_draw_method == callable_cache.get(p_tree_item)){ + return; + } + + Callable draw_callback = callable_mp(this, &TreeSearch::_draw_highlight_item).bind(parent_draw_method); + + // -- this is necessary because of the modularity of this implementation + // cache render properties of entry + String cached_text = p_tree_item->get_text(0); + Ref cached_icon = p_tree_item->get_icon(0); + int cached_max_width = p_tree_item->get_icon_max_width(0); + callable_cache[p_tree_item] = draw_callback; + + // this removes render properties in entry + p_tree_item->set_custom_draw_callback(0, draw_callback); + p_tree_item->set_cell_mode(0, TreeItem::CELL_MODE_CUSTOM); + + // restore render properties + p_tree_item->set_text(0, cached_text); + p_tree_item->set_icon(0, cached_icon); + p_tree_item->set_icon_max_width(0, cached_max_width); +} + // custom draw callback for highlighting (bind the parent_drw_method to this) void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) { if (!p_tree_item) { @@ -143,7 +181,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Calla Vector2 rect_offset = Vector2(substring_before_size.x, 0); rect_offset.x += p_tree_item->get_icon_max_width(0); - rect_offset.x += (h_sep + 4. * EDSCALE); // TODO: Find better way to determine texts x-offset + rect_offset.x += (h_sep + 4. * EDSCALE); rect_offset.y = (p_rect.size.y - substring_match_size.y) / 2; // center box vertically draw_rect.position += rect_offset - PADDING / 2; @@ -354,24 +392,26 @@ void TreeSearch::notify_item_edited(TreeItem *item) { if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) { return; } - - if (!callable_cache.has(item) || item->get_custom_draw_callback(0) == callable_cache.get(item)) { - return; - } - - item->set_custom_draw_callback(0, callable_cache.get(item)); + _highlight_tree_item(item); } // Call this as a post-processing step for the already constructed tree. void TreeSearch::update_search(Tree *p_tree) { ERR_FAIL_COND(!search_panel || !p_tree); - // ignore if panel not visible or no search string is given. + tree_reference = p_tree; + if (!search_panel->is_visible() || search_panel->get_text().length() == 0) { + // clear and redraw if search was active recently. + if (was_searched_recently) { + _clear_filter(); + matching_entries.clear(); + was_searched_recently = false; + p_tree->queue_redraw(); + } return; } - - tree_reference = p_tree; + was_searched_recently = true; String search_mask = search_panel->get_text(); TreeSearchMode search_mode = search_panel->get_search_mode(); @@ -380,11 +420,16 @@ void TreeSearch::update_search(Tree *p_tree) { _update_matching_entries(search_mask); _update_number_matches(); - _highlight_tree(search_mask); - + _clear_filter(); + _highlight_tree(); if (search_mode == TreeSearchMode::FILTER) { _filter_tree(search_mask); + was_filtered_recently = true; + } else if (was_filtered_recently) { + _clear_filter(); + was_filtered_recently = false; } + _clean_callable_cache(); } TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { diff --git a/editor/tree_search.h b/editor/tree_search.h index 3c1174e..1dbb2d2 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -53,14 +53,23 @@ private: // For TaskTree: These are updated when the tree is updated through TaskTree::_create_tree. Tree *tree_reference; + Vector ordered_tree_items; Vector matching_entries; HashMap number_matches; HashMap callable_cache; + bool was_searched_recently = false; // Performance + bool was_filtered_recently = false; // Performance + + void _clean_callable_cache(); + // Update_search() calls these void _filter_tree(const String &p_search_mask); - void _highlight_tree(const String &p_search_mask); + void _clear_filter(); + + void _highlight_tree(); + void _highlight_tree_item(TreeItem *p_tree_item); // Custom draw-Callback (bind inherited Callable). void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method); From 6200f61162e09b7f04f58407a7803866899867fe Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 16:48:57 +0200 Subject: [PATCH 29/45] Fix descendent counter in TreeSearch --- editor/tree_search.cpp | 1 + editor/tree_search.h | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index e0494d1..ae775b5 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -405,6 +405,7 @@ void TreeSearch::update_search(Tree *p_tree) { // clear and redraw if search was active recently. if (was_searched_recently) { _clear_filter(); + number_matches.clear(); matching_entries.clear(); was_searched_recently = false; p_tree->queue_redraw(); diff --git a/editor/tree_search.h b/editor/tree_search.h index 1dbb2d2..21ff51f 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -53,10 +53,13 @@ private: // For TaskTree: These are updated when the tree is updated through TaskTree::_create_tree. Tree *tree_reference; - + // linearized ordering of tree items. Vector ordered_tree_items; + // entires that match the search mask. Vector matching_entries; + // number of descendant matches for each tree item. HashMap number_matches; + // custom draw-callbacks for each tree item. HashMap callable_cache; bool was_searched_recently = false; // Performance From 5a66160bcec3167f959ba49acc83bb1e5efacb4f Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 15:57:13 +0000 Subject: [PATCH 30/45] Add `const` qualifiers where possible --- editor/tree_search.cpp | 12 ++++++------ editor/tree_search.h | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index ae775b5..4249dfa 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -114,7 +114,7 @@ void TreeSearch::_highlight_tree_item(TreeItem *p_tree_item) { } // if the cached draw method is already applied, do nothing. - if (callable_cache.has(p_tree_item) && parent_draw_method == callable_cache.get(p_tree_item)){ + if (callable_cache.has(p_tree_item) && parent_draw_method == callable_cache.get(p_tree_item)) { return; } @@ -138,7 +138,7 @@ void TreeSearch::_highlight_tree_item(TreeItem *p_tree_item) { } // custom draw callback for highlighting (bind the parent_drw_method to this) -void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) { +void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, const Rect2 p_rect, const Callable p_parent_draw_method) { if (!p_tree_item) { return; } @@ -250,12 +250,12 @@ void TreeSearch::_update_number_matches() { } } -String TreeSearch::_get_search_mask() { +String TreeSearch::_get_search_mask() const { ERR_FAIL_COND_V(!search_panel, ""); return search_panel->get_text(); } -void TreeSearch::_find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) { +void TreeSearch::_find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) const { if (!p_tree_item) { return; } @@ -381,7 +381,7 @@ void TreeSearch::_select_next_match() { } template -inline bool TreeSearch::_vector_has_bsearch(Vector p_vec, T *element) { +inline bool TreeSearch::_vector_has_bsearch(Vector &p_vec, T *element) const { int idx = p_vec.bsearch(element, true); bool in_array = idx >= 0 && idx < p_vec.size(); @@ -517,7 +517,7 @@ TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { return TreeSearch::TreeSearchMode::FILTER; } -String TreeSearchPanel::get_text() { +String TreeSearchPanel::get_text() const { if (!line_edit_search) { return String(); } diff --git a/editor/tree_search.h b/editor/tree_search.h index 21ff51f..6e6ea32 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -75,22 +75,23 @@ private: void _highlight_tree_item(TreeItem *p_tree_item); // Custom draw-Callback (bind inherited Callable). - void _draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method); + void _draw_highlight_item(TreeItem *p_tree_item, const Rect2 p_rect, const Callable p_parent_draw_method); void _update_matching_entries(const String &p_search_mask); void _update_ordered_tree_items(TreeItem *p_tree_item); void _update_number_matches(); - void _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum); - String _get_search_mask(); + void _find_matching_entries(TreeItem *p_tree_item, const String &p_search_mask, Vector &p_accum) const; + String _get_search_mask() const; StringSearchIndices _substring_bounds(const String &p_searchable, const String &p_search_mask) const; void _select_item(TreeItem *p_item); void _select_first_match(); void _select_next_match(); + // TODO: make p_vec `const` once Vector::bsearch is const. See: https://github.com/godotengine/godot/pull/90341 template - bool _vector_has_bsearch(Vector p_vec, T *element); + bool _vector_has_bsearch(Vector &p_vec, T *element) const; protected: static void _bind_methods() {} @@ -121,8 +122,7 @@ private: CheckBox *check_button_filter_highlight; void _initialize_controls(); void _add_spacer(float width_multiplier = 1.f); - - void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect); + void _notification(int p_what); protected: @@ -130,7 +130,7 @@ protected: public: TreeSearch::TreeSearchMode get_search_mode(); - String get_text(); + String get_text() const; void show_and_focus(); TreeSearchPanel(); }; From 585d9663d60cdf80222c2fa33359a9d6edcd1a02 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 18:12:18 +0200 Subject: [PATCH 31/45] Fix optimization through which the parent count is not drawn --- editor/tree_search.cpp | 5 +++-- editor/tree_search.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 4249dfa..599544e 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -93,8 +93,9 @@ void TreeSearch::_clear_filter() { void TreeSearch::_highlight_tree() { ERR_FAIL_COND(!tree_reference); - for (int i = 0; i < matching_entries.size(); i++) { - TreeItem *tree_item = matching_entries[i]; + // This might not be the prettiest, but it is the most efficient solution probably. + for (HashMap::Iterator it = number_matches.begin(); it != number_matches.end(); ++it) { + TreeItem *tree_item = it->key; _highlight_tree_item(tree_item); } tree_reference->queue_redraw(); diff --git a/editor/tree_search.h b/editor/tree_search.h index 6e6ea32..676ccc7 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -56,6 +56,7 @@ private: // linearized ordering of tree items. Vector ordered_tree_items; // entires that match the search mask. + // TODO: Decide if this can be removed. It can be implicitly infered from number_matches. Vector matching_entries; // number of descendant matches for each tree item. HashMap number_matches; @@ -122,7 +123,7 @@ private: CheckBox *check_button_filter_highlight; void _initialize_controls(); void _add_spacer(float width_multiplier = 1.f); - + void _notification(int p_what); protected: From f7d546fc3cbb489e4059ee90b195ad8e9e9957ce Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 16:26:18 +0000 Subject: [PATCH 32/45] Add one method-const qualifier to TreeSearchPanel --- editor/tree_search.cpp | 2 +- editor/tree_search.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 599544e..b026253 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -511,7 +511,7 @@ TreeSearchPanel::TreeSearchPanel() { set_visible(false); } -TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() { +TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() const{ if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { return TreeSearch::TreeSearchMode::HIGHLIGHT; } diff --git a/editor/tree_search.h b/editor/tree_search.h index 676ccc7..9d78bf7 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -56,7 +56,7 @@ private: // linearized ordering of tree items. Vector ordered_tree_items; // entires that match the search mask. - // TODO: Decide if this can be removed. It can be implicitly infered from number_matches. + // TODO: Decide if this can be removed. It can be implicitly inferred from number_matches. Vector matching_entries; // number of descendant matches for each tree item. HashMap number_matches; @@ -90,7 +90,7 @@ private: void _select_first_match(); void _select_next_match(); - // TODO: make p_vec `const` once Vector::bsearch is const. See: https://github.com/godotengine/godot/pull/90341 + // TODO: make p_vec ref `const` once Vector::bsearch is const. See: https://github.com/godotengine/godot/pull/90341 template bool _vector_has_bsearch(Vector &p_vec, T *element) const; @@ -130,7 +130,7 @@ protected: static void _bind_methods(); public: - TreeSearch::TreeSearchMode get_search_mode(); + TreeSearch::TreeSearchMode get_search_mode() const; String get_text() const; void show_and_focus(); TreeSearchPanel(); From e8de3adc50e0c18c001f645aa27474b940675cf7 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 16:43:48 +0000 Subject: [PATCH 33/45] Improve formatting of TreeSearch --- editor/tree_search.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index b026253..df5b76e 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -356,12 +356,13 @@ void TreeSearch::_select_next_match() { if (matching_entries.size() == 0) { return; } - TreeItem *selected = tree_reference->get_selected(); // we care about a single item here. + TreeItem *selected = tree_reference->get_selected(); if (!selected) { _select_first_match(); return; } + // find [selected_idx] among ordered_tree_items int selected_idx = -1; for (int i = 0; i < ordered_tree_items.size(); i++) { if (ordered_tree_items[i] == selected) { @@ -370,7 +371,7 @@ void TreeSearch::_select_next_match() { } } - // find the best fitting entry. + // find first entry after [selected_idx]. for (int i = MAX(0, selected_idx) + 1; i < ordered_tree_items.size(); i++) { TreeItem *item = ordered_tree_items[i]; if (_vector_has_bsearch(matching_entries, item)) { @@ -378,7 +379,8 @@ void TreeSearch::_select_next_match() { return; } } - _select_first_match(); // wrap around. + // wrap around. + _select_first_match(); } template @@ -511,7 +513,7 @@ TreeSearchPanel::TreeSearchPanel() { set_visible(false); } -TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() const{ +TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() const { if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { return TreeSearch::TreeSearchMode::HIGHLIGHT; } From 20be87904a50ab13f91bd4c5f3ca72ff3084f527 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Sun, 29 Sep 2024 21:47:38 +0200 Subject: [PATCH 34/45] Focus tree when search panel is closed --- editor/tree_search.cpp | 7 +++++++ editor/tree_search.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index df5b76e..160c614 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -383,6 +383,12 @@ void TreeSearch::_select_next_match() { _select_first_match(); } +void TreeSearch::_on_search_panel_visibility_changed() { + if (tree_reference && !search_panel->is_visible()) { + tree_reference->grab_focus(); + } +} + template inline bool TreeSearch::_vector_has_bsearch(Vector &p_vec, T *element) const { int idx = p_vec.bsearch(element, true); @@ -439,6 +445,7 @@ void TreeSearch::update_search(Tree *p_tree) { TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { search_panel = p_search_panel; search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); + search_panel->connect("visibility_changed", callable_mp(this, &TreeSearch::_on_search_panel_visibility_changed)); } /* !TreeSearch */ diff --git a/editor/tree_search.h b/editor/tree_search.h index 9d78bf7..2b7cf63 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -90,6 +90,8 @@ private: void _select_first_match(); void _select_next_match(); + void _on_search_panel_visibility_changed(); + // TODO: make p_vec ref `const` once Vector::bsearch is const. See: https://github.com/godotengine/godot/pull/90341 template bool _vector_has_bsearch(Vector &p_vec, T *element) const; From 10c8f58ca9b9e3c9034b7eb5b5dc52e2677a18c7 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Mon, 30 Sep 2024 16:13:36 +0200 Subject: [PATCH 35/45] Remember SearchInfo for each tab. Focus tree when *user* closes SearchPanel --- editor/limbo_ai_editor_plugin.cpp | 18 +++++++++++++++- editor/limbo_ai_editor_plugin.h | 3 ++- editor/task_tree.cpp | 23 +++++++++++++++++---- editor/task_tree.h | 7 +++++-- editor/tree_search.cpp | 34 ++++++++++++++++++++++++------- editor/tree_search.h | 13 ++++++++++-- 6 files changed, 81 insertions(+), 17 deletions(-) diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 57becb6..648f89a 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -261,6 +261,10 @@ void LimboAIEditor::edit_bt(const Ref &p_behavior_tree, bool p_for p_behavior_tree->editor_set_section_unfold("blackboard_plan", true); p_behavior_tree->notify_property_list_changed(); #endif // LIMBOAI_MODULE + // Remember current search info. + if (idx_history >= 0 && idx_history < history.size()) { + tab_search_context.insert(history[idx_history], task_tree->tree_search_get_search_info()); + } task_tree->load_bt(p_behavior_tree); @@ -280,6 +284,18 @@ void LimboAIEditor::edit_bt(const Ref &p_behavior_tree, bool p_for task_tree->show(); task_palette->show(); + // Restore search info from [tab_search_context]. + if (idx_history >= 0 && idx_history < history.size()) { + // info for BehaviorTree available. Restore! + if (tab_search_context.has(history[idx_history])) { + task_tree->tree_search_set_search_info(tab_search_context[history[idx_history]]); + } + // new SearchContext. + else { + task_tree->tree_search_set_search_info(TreeSearch::SearchInfo()); + } + } + _update_tabs(); } @@ -1324,7 +1340,7 @@ void LimboAIEditor::_update_misc_menu() { misc_menu->add_item( FILE_EXISTS(_get_script_template_path()) ? TTR("Edit Script Template") : TTR("Create Script Template"), MISC_CREATE_SCRIPT_TEMPLATE); - + misc_menu->add_separator(); misc_menu->add_icon_shortcut(theme_cache.search_icon, LW_GET_SHORTCUT("limbo_ai/find_task"), MISC_SEARCH_TREE); } diff --git a/editor/limbo_ai_editor_plugin.h b/editor/limbo_ai_editor_plugin.h index 1868175..42e2839 100644 --- a/editor/limbo_ai_editor_plugin.h +++ b/editor/limbo_ai_editor_plugin.h @@ -48,6 +48,7 @@ #ifdef LIMBOAI_GDEXTENSION #include "godot_cpp/classes/accept_dialog.hpp" +#include #include #include #include @@ -64,7 +65,6 @@ #include #include #include -#include using namespace godot; @@ -143,6 +143,7 @@ private: EditorLayout editor_layout; Vector> history; int idx_history; + HashMap, TreeSearch::SearchInfo> tab_search_context; bool updating_tabs = false; bool request_update_tabs = false; HashSet> dirty; diff --git a/editor/task_tree.cpp b/editor/task_tree.cpp index a976ca0..efd57b2 100644 --- a/editor/task_tree.cpp +++ b/editor/task_tree.cpp @@ -47,9 +47,9 @@ TreeItem *TaskTree::_create_tree(const Ref &p_task, TreeItem *p_parent, _create_tree(p_task->get_child(i), item); } _update_item(item); - + // update TreeSearch if root task was created - if (tree->get_root() == item){ + if (tree->get_root() == item) { tree_search->update_search(tree); } @@ -546,7 +546,6 @@ void TaskTree::_notification(int p_what) { _update_tree(); } break; } - } void TaskTree::_bind_methods() { @@ -573,11 +572,27 @@ void TaskTree::_bind_methods() { PropertyInfo(Variant::INT, "type"))); } +// TreeSearch API void TaskTree::tree_search_show_and_focus() { ERR_FAIL_NULL(tree_search); - tree_search_panel->show_and_focus(); + tree_search_panel->set_visible(true); + tree_search_panel->focus_editor(); } +TreeSearch::SearchInfo TaskTree::tree_search_get_search_info() const { + if (!tree_search.is_valid()) { + return TreeSearch::SearchInfo(); + } + return tree_search_panel->get_search_info(); +} + +void TaskTree::tree_search_set_search_info(const TreeSearch::SearchInfo &p_search_info) { + ERR_FAIL_NULL(tree_search); + tree_search_panel->set_search_info(p_search_info); +} + +// TreeSearch Api ^ + TaskTree::TaskTree() { editable = true; updating_tree = false; diff --git a/editor/task_tree.h b/editor/task_tree.h index 2373e7f..efb7241 100644 --- a/editor/task_tree.h +++ b/editor/task_tree.h @@ -103,13 +103,16 @@ public: Ref get_selected() const; Vector> get_selected_tasks() const; void clear_selection(); - void tree_search_show_and_focus(); - Rect2 get_selected_probability_rect() const; double get_selected_probability_weight() const; double get_selected_probability_percent() const; bool selected_has_probability() const; + // TreeSearch API + void tree_search_show_and_focus(); + TreeSearch::SearchInfo tree_search_get_search_info() const; + void tree_search_set_search_info(const TreeSearch::SearchInfo &p_search_info); + virtual bool editor_can_reload_from_file() { return false; } TaskTree(); diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 160c614..70959b3 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -383,10 +383,11 @@ void TreeSearch::_select_next_match() { _select_first_match(); } -void TreeSearch::_on_search_panel_visibility_changed() { - if (tree_reference && !search_panel->is_visible()) { - tree_reference->grab_focus(); +void TreeSearch::_on_search_panel_closed() { + if (!tree_reference) { + return; } + tree_reference->grab_focus(); } template @@ -445,7 +446,7 @@ void TreeSearch::update_search(Tree *p_tree) { TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { search_panel = p_search_panel; search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); - search_panel->connect("visibility_changed", callable_mp(this, &TreeSearch::_on_search_panel_visibility_changed)); + search_panel->connect("closed", callable_mp(this, &TreeSearch::_on_search_panel_closed)); } /* !TreeSearch */ @@ -491,8 +492,8 @@ void TreeSearchPanel::_notification(int p_what) { case NOTIFICATION_READY: { // close callbacks close_button->connect("pressed", Callable(this, "set_visible").bind(false)); + close_button->connect("pressed", Callable(this, "emit_signal").bind("closed")); close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); - // search callbacks Callable c_update_requested = Callable(this, "emit_signal").bind("update_requested"); Callable c_text_submitted = Callable((Object *)this, "emit_signal").bind("text_submitted"); @@ -513,6 +514,7 @@ void TreeSearchPanel::_notification(int p_what) { void TreeSearchPanel::_bind_methods() { ADD_SIGNAL(MethodInfo("update_requested")); ADD_SIGNAL(MethodInfo("text_submitted")); + ADD_SIGNAL(MethodInfo("closed")); } TreeSearchPanel::TreeSearchPanel() { @@ -534,8 +536,26 @@ String TreeSearchPanel::get_text() const { return line_edit_search->get_text(); } -void TreeSearchPanel::show_and_focus() { - set_visible(true); +TreeSearch::SearchInfo TreeSearchPanel::get_search_info() const { + TreeSearch::SearchInfo result; + result.search_mask = get_text(); + result.search_mode = get_search_mode(); + result.visible = is_visible(); + return result; +} + +void TreeSearchPanel::set_search_info(TreeSearch::SearchInfo p_search_info) { + line_edit_search->set_text(p_search_info.search_mask); + check_button_filter_highlight->set_pressed(p_search_info.search_mode == TreeSearch::TreeSearchMode::FILTER); + set_visible(p_search_info.visible); + emit_signal("update_requested"); +} + +void TreeSearchPanel::set_text(const String &p_text) { + line_edit_search->set_text(p_text); +} + +void TreeSearchPanel::focus_editor() { line_edit_search->grab_focus(); } diff --git a/editor/tree_search.h b/editor/tree_search.h index 2b7cf63..917be16 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -90,7 +90,7 @@ private: void _select_first_match(); void _select_next_match(); - void _on_search_panel_visibility_changed(); + void _on_search_panel_closed(); // TODO: make p_vec ref `const` once Vector::bsearch is const. See: https://github.com/godotengine/godot/pull/90341 template @@ -105,6 +105,12 @@ public: FILTER = 1 }; + struct SearchInfo { + String search_mask; + TreeSearchMode search_mode; + bool visible; + }; + void update_search(Tree *p_tree); void notify_item_edited(TreeItem *p_item); @@ -134,7 +140,10 @@ protected: public: TreeSearch::TreeSearchMode get_search_mode() const; String get_text() const; - void show_and_focus(); + TreeSearch::SearchInfo get_search_info() const; + void set_search_info(TreeSearch::SearchInfo p_search_info); + void set_text(const String &p_text); + void focus_editor(); TreeSearchPanel(); }; From 683834f58e217230f0f0b8d9647fc34982ace679 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Mon, 30 Sep 2024 14:22:23 +0000 Subject: [PATCH 36/45] Drop `TreeSearchPanel::set_text`. --- editor/tree_search.cpp | 6 +----- editor/tree_search.h | 5 ++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 70959b3..3462d80 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -544,17 +544,13 @@ TreeSearch::SearchInfo TreeSearchPanel::get_search_info() const { return result; } -void TreeSearchPanel::set_search_info(TreeSearch::SearchInfo p_search_info) { +void TreeSearchPanel::set_search_info(const TreeSearch::SearchInfo &p_search_info) { line_edit_search->set_text(p_search_info.search_mask); check_button_filter_highlight->set_pressed(p_search_info.search_mode == TreeSearch::TreeSearchMode::FILTER); set_visible(p_search_info.visible); emit_signal("update_requested"); } -void TreeSearchPanel::set_text(const String &p_text) { - line_edit_search->set_text(p_text); -} - void TreeSearchPanel::focus_editor() { line_edit_search->grab_focus(); } diff --git a/editor/tree_search.h b/editor/tree_search.h index 917be16..0501514 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -138,11 +138,10 @@ protected: static void _bind_methods(); public: - TreeSearch::TreeSearchMode get_search_mode() const; String get_text() const; + TreeSearch::TreeSearchMode get_search_mode() const; TreeSearch::SearchInfo get_search_info() const; - void set_search_info(TreeSearch::SearchInfo p_search_info); - void set_text(const String &p_text); + void set_search_info(const TreeSearch::SearchInfo &p_search_info); void focus_editor(); TreeSearchPanel(); }; From 8d781da1a64133921ac3b46bd4910fcf0829428c Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Mon, 30 Sep 2024 17:07:26 +0000 Subject: [PATCH 37/45] Replace c-strings with LW_NAME for signals in TreeSearch(Panel) --- editor/tree_search.cpp | 22 +++++++++++----------- util/limbo_string_names.cpp | 2 ++ util/limbo_string_names.h | 2 ++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 3462d80..03a11cb 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -445,8 +445,8 @@ void TreeSearch::update_search(Tree *p_tree) { TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { search_panel = p_search_panel; - search_panel->connect("text_submitted", callable_mp(this, &TreeSearch::_select_next_match)); - search_panel->connect("closed", callable_mp(this, &TreeSearch::_on_search_panel_closed)); + search_panel->connect(LW_NAME(text_submitted), callable_mp(this, &TreeSearch::_select_next_match)); + search_panel->connect(LW_NAME(Close), callable_mp(this, &TreeSearch::_on_search_panel_closed)); } /* !TreeSearch */ @@ -491,16 +491,16 @@ void TreeSearchPanel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { // close callbacks - close_button->connect("pressed", Callable(this, "set_visible").bind(false)); - close_button->connect("pressed", Callable(this, "emit_signal").bind("closed")); + close_button->connect(LW_NAME(pressed), Callable(this, LW_NAME(set_visible)).bind(false)); + close_button->connect(LW_NAME(pressed), Callable(this, LW_NAME(emit_signal)).bind(LW_NAME(Close))); close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search")); // search callbacks - Callable c_update_requested = Callable(this, "emit_signal").bind("update_requested"); - Callable c_text_submitted = Callable((Object *)this, "emit_signal").bind("text_submitted"); + Callable c_update_requested = Callable(this, LW_NAME(emit_signal)).bind("update_requested"); + Callable c_text_submitted = Callable((Object *)this, LW_NAME(emit_signal)).bind(LW_NAME(text_submitted)); - line_edit_search->connect("text_changed", c_update_requested.unbind(1)); - check_button_filter_highlight->connect("pressed", c_update_requested); - line_edit_search->connect("text_submitted", c_text_submitted.unbind(1)); + line_edit_search->connect(LW_NAME(text_changed), c_update_requested.unbind(1)); + check_button_filter_highlight->connect(LW_NAME(pressed), c_update_requested); + line_edit_search->connect(LW_NAME(text_submitted), c_text_submitted.unbind(1)); break; } case NOTIFICATION_THEME_CHANGED: { @@ -513,8 +513,8 @@ void TreeSearchPanel::_notification(int p_what) { void TreeSearchPanel::_bind_methods() { ADD_SIGNAL(MethodInfo("update_requested")); - ADD_SIGNAL(MethodInfo("text_submitted")); - ADD_SIGNAL(MethodInfo("closed")); + ADD_SIGNAL(MethodInfo(LW_NAME(text_submitted))); + ADD_SIGNAL(MethodInfo(LW_NAME(Close))); } TreeSearchPanel::TreeSearchPanel() { diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index 1a13566..97de228 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -59,6 +59,7 @@ LimboStringNames::LimboStringNames() { EditorFonts = SN("EditorFonts"); EditorIcons = SN("EditorIcons"); EditorStyles = SN("EditorStyles"); + emit_signal = SN("emit_signal"); entered = SN("entered"); error_value = SN("error_value"); EVENT_FAILURE = SN("failure"); @@ -124,6 +125,7 @@ LimboStringNames::LimboStringNames() { separation = SN("separation"); set_custom_name = SN("set_custom_name"); set_root_task = SN("set_root_task"); + set_visible = SN("set_visible"); set_v_scroll = SN("set_v_scroll"); setup = SN("setup"); started = SN("started"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index 6e4c0ba..d1f7036 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -75,6 +75,7 @@ public: StringName EditorFonts; StringName EditorIcons; StringName EditorStyles; + StringName emit_signal; StringName entered; StringName error_value; StringName EVENT_FAILURE; @@ -140,6 +141,7 @@ public: StringName separation; StringName set_custom_name; StringName set_root_task; + StringName set_visible; StringName set_v_scroll; StringName setup; StringName started; From e6e1addbcb719fa1cacfcb593a760fe637ad3823 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Mon, 30 Sep 2024 19:23:49 +0200 Subject: [PATCH 38/45] Optimize TreeSearch::_filter_tree for performance --- editor/tree_search.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 03a11cb..0b3a8f5 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -68,10 +68,16 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { } TreeItem *first_counting_ancestor = cur_item; + bool parents_visible = true; while (first_counting_ancestor && !number_matches.has(first_counting_ancestor)) { + // Performance: we only need to check the first visible ancestor. This is already optimal because of the ordering in ordered_tree_items. + if (!first_counting_ancestor->is_visible()){ + parents_visible = false; + break; + } first_counting_ancestor = first_counting_ancestor->get_parent(); } - if (!first_counting_ancestor || !_vector_has_bsearch(matching_entries, first_counting_ancestor)) { + if (!parents_visible || !first_counting_ancestor || !_vector_has_bsearch(matching_entries, first_counting_ancestor)) { cur_item->set_visible(false); } } From 74cf9855a7fed51ed6e373ed14599a64a62eae46 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 00:05:21 +0200 Subject: [PATCH 39/45] Fix tree filtering with new approach --- editor/tree_search.cpp | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 0b3a8f5..f35d047 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -56,29 +56,41 @@ void TreeSearch::_clean_callable_cache() { } void TreeSearch::_filter_tree(const String &p_search_mask) { - if (matching_entries.size() == 0) { + if (matching_entries.is_empty()) { return; } + Vector item_visibilities; + item_visibilities.resize(ordered_tree_items.size()); + + // Make all entries visible that have any matching descendents. O(n) for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *cur_item = ordered_tree_items[i]; - - if (number_matches.has(cur_item)) { - continue; + TreeItem *entry = ordered_tree_items[i]; + if (number_matches.has(entry) && number_matches[entry] > 0) { + item_visibilities.set(i, true); } + } - TreeItem *first_counting_ancestor = cur_item; - bool parents_visible = true; - while (first_counting_ancestor && !number_matches.has(first_counting_ancestor)) { - // Performance: we only need to check the first visible ancestor. This is already optimal because of the ordering in ordered_tree_items. - if (!first_counting_ancestor->is_visible()){ - parents_visible = false; - break; + // Make all descendents of matching entries visible. O(n) * log(|matching_entries|) + for (int i = 0; i < ordered_tree_items.size(); i++) { + TreeItem *entry = ordered_tree_items[i]; + TreeItem *next_entry = entry->get_next(); + if (!next_entry && i < ordered_tree_items.size() - 1) { + next_entry = ordered_tree_items[i + 1]; + } + if (_vector_has_bsearch(matching_entries, entry)) { + int j = i; + for (; j < ordered_tree_items.size() && ordered_tree_items[j] != next_entry; j++) { + item_visibilities.set(j, true); } - first_counting_ancestor = first_counting_ancestor->get_parent(); + i = j - 1; // every entry is only processed once. } - if (!parents_visible || !first_counting_ancestor || !_vector_has_bsearch(matching_entries, first_counting_ancestor)) { - cur_item->set_visible(false); + } + + // apply visibility. O(n) + for (int i = 0; i < ordered_tree_items.size(); i++) { + if (!item_visibilities[i]) { + ordered_tree_items[i]->set_visible(false); } } } From 4f7996b1ea40f497ada1d79ede522bb7870d576a Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 01:05:44 +0200 Subject: [PATCH 40/45] Fix new filtering approach for TreeSearch --- editor/tree_search.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index f35d047..785cd10 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -62,6 +62,7 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { Vector item_visibilities; item_visibilities.resize(ordered_tree_items.size()); + item_visibilities.fill(false); // Make all entries visible that have any matching descendents. O(n) for (int i = 0; i < ordered_tree_items.size(); i++) { @@ -74,11 +75,19 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { // Make all descendents of matching entries visible. O(n) * log(|matching_entries|) for (int i = 0; i < ordered_tree_items.size(); i++) { TreeItem *entry = ordered_tree_items[i]; - TreeItem *next_entry = entry->get_next(); - if (!next_entry && i < ordered_tree_items.size() - 1) { - next_entry = ordered_tree_items[i + 1]; - } if (_vector_has_bsearch(matching_entries, entry)) { + // the [next_entry] at the same depth or depth above. + TreeItem *next_entry = entry->get_next(); + + // search above current depth if no [next_entry]. + while (!next_entry && entry) { + entry = entry->get_parent(); + } + if (entry) { + next_entry = entry->get_next(); + } + + // mark visible all successors upto next entry at the same depth or above int j = i; for (; j < ordered_tree_items.size() && ordered_tree_items[j] != next_entry; j++) { item_visibilities.set(j, true); @@ -87,7 +96,7 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { } } - // apply visibility. O(n) + // Apply visibility. O(n) for (int i = 0; i < ordered_tree_items.size(); i++) { if (!item_visibilities[i]) { ordered_tree_items[i]->set_visible(false); @@ -95,7 +104,7 @@ void TreeSearch::_filter_tree(const String &p_search_mask) { } } -// makes all tree items visible. +// Makes all tree items visible. void TreeSearch::_clear_filter() { ERR_FAIL_COND(!tree_reference); Vector items = { tree_reference->get_root() }; From 540bb585a8903057475ac173c2b6cb8506f58672 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 17:04:46 +0200 Subject: [PATCH 41/45] Refactor tree filtering logic for correctness and efficiency --- editor/tree_search.cpp | 84 ++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 785cd10..2e9b46d 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -25,6 +25,7 @@ #include "scene/main/viewport.h" #include "scene/resources/font.h" #include "scene/resources/style_box_flat.h" +#include "core/templates/hash_set.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION @@ -34,6 +35,7 @@ #include #include #include +#include #endif // LIMBOAI_GDEXTENSION @@ -55,51 +57,60 @@ void TreeSearch::_clean_callable_cache() { callable_cache = new_callable_cache; } +// Makes all tree items invisible that don't match the following criteria: +// 1. is parent of matching tree_item +// 2. is matching tree_item +// 3. is any descendent of matching tree item void TreeSearch::_filter_tree(const String &p_search_mask) { + ERR_FAIL_COND(!tree_reference); + + // We don't filter if no search_mask is given. if (matching_entries.is_empty()) { return; } - Vector item_visibilities; - item_visibilities.resize(ordered_tree_items.size()); - item_visibilities.fill(false); + HashSet visible_items; + visible_items.reserve(ordered_tree_items.size()); - // Make all entries visible that have any matching descendents. O(n) - for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *entry = ordered_tree_items[i]; - if (number_matches.has(entry) && number_matches[entry] > 0) { - item_visibilities.set(i, true); + Vector items = { tree_reference->get_root() }; + + for (int idx = 0; idx < items.size(); idx++) { + TreeItem *cur_item = items[idx]; + if (_vector_has_bsearch(matching_entries, cur_item)) { + visible_items.insert(cur_item); + TreeItem *parent_of_match = cur_item->get_parent(); + + // 1. mark all parents visible + while (parent_of_match) { + visible_items.insert(parent_of_match); + parent_of_match = parent_of_match->get_parent(); + } + + // for 2. and 3. we collect all children of match. + Vector matching_entries_descendents = { cur_item }; + + // 2. and 3. mark all descendents visible + for (int i = 0; i < matching_entries_descendents.size(); i++) { + TreeItem *cur_descendent_item = matching_entries_descendents[i]; + visible_items.insert(cur_descendent_item); + + // 3. collect all children of descendent + for (int i = 0; i < cur_descendent_item->get_child_count(); i++) { + matching_entries_descendents.push_back(cur_descendent_item->get_child(i)); + } + } + + } else { // not a matching entry: process children + for (int i = 0; i < cur_item->get_child_count(); i++) { + items.push_back(cur_item->get_child(i)); + } } } - // Make all descendents of matching entries visible. O(n) * log(|matching_entries|) for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *entry = ordered_tree_items[i]; - if (_vector_has_bsearch(matching_entries, entry)) { - // the [next_entry] at the same depth or depth above. - TreeItem *next_entry = entry->get_next(); - - // search above current depth if no [next_entry]. - while (!next_entry && entry) { - entry = entry->get_parent(); - } - if (entry) { - next_entry = entry->get_next(); - } - - // mark visible all successors upto next entry at the same depth or above - int j = i; - for (; j < ordered_tree_items.size() && ordered_tree_items[j] != next_entry; j++) { - item_visibilities.set(j, true); - } - i = j - 1; // every entry is only processed once. - } - } - - // Apply visibility. O(n) - for (int i = 0; i < ordered_tree_items.size(); i++) { - if (!item_visibilities[i]) { - ordered_tree_items[i]->set_visible(false); + TreeItem *cur_item = ordered_tree_items[i]; + if (!visible_items.has(cur_item)) { + cur_item->set_visible(false); } } } @@ -170,6 +181,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, const Rect2 p_rect, if (!p_tree_item) { return; } + // call any parent draw methods such as for probability FIRST. p_parent_draw_method.call(p_tree_item, p_rect); @@ -584,4 +596,4 @@ void TreeSearchPanel::focus_editor() { /* !TreeSearchPanel */ -#endif // TOOLS_ENABLED \ No newline at end of file +#endif // TOOLS_ENABLED From 146635126a6dce2626be242896b9c92afa717228 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 17:44:31 +0200 Subject: [PATCH 42/45] Refactor _filter_items again for readability --- editor/tree_search.cpp | 81 ++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 2e9b46d..18fae15 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -19,13 +19,13 @@ #ifdef LIMBOAI_MODULE #include "core/math/math_funcs.h" +#include "core/templates/hash_set.h" #include "editor/editor_interface.h" #include "editor/themes/editor_scale.h" #include "scene/gui/separator.h" #include "scene/main/viewport.h" #include "scene/resources/font.h" #include "scene/resources/style_box_flat.h" -#include "core/templates/hash_set.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION @@ -58,60 +58,55 @@ void TreeSearch::_clean_callable_cache() { } // Makes all tree items invisible that don't match the following criteria: -// 1. is parent of matching tree_item -// 2. is matching tree_item -// 3. is any descendent of matching tree item +// 1. is a matching tree_item +// 2. is a parent of a matching tree_item +// 3. is any descendant of a matching tree_item void TreeSearch::_filter_tree(const String &p_search_mask) { - ERR_FAIL_COND(!tree_reference); - - // We don't filter if no search_mask is given. - if (matching_entries.is_empty()) { + if (number_matches.size() == 0){ return; } + HashSet visible_as_parent; + HashSet visible_as_descendant; - HashSet visible_items; - visible_items.reserve(ordered_tree_items.size()); + // Mark matching items and all their ancestors (1, 2) + for (int i = 0; i < matching_entries.size(); i++) { + TreeItem *cur_match = matching_entries[i]; + TreeItem *current = cur_match; + while (current != nullptr && !visible_as_parent.has(current)) { + visible_as_parent.insert(current); + current = current->get_parent(); + } + } - Vector items = { tree_reference->get_root() }; + // Mark matching items and all their descendants (1, 3) + for (int i = 0; i < matching_entries.size(); i++) { + TreeItem *cur_match = matching_entries[i]; + if (visible_as_descendant.has(cur_match)) { + continue; + } + // Descendents + Vector descendent_list = { cur_match }; - for (int idx = 0; idx < items.size(); idx++) { - TreeItem *cur_item = items[idx]; - if (_vector_has_bsearch(matching_entries, cur_item)) { - visible_items.insert(cur_item); - TreeItem *parent_of_match = cur_item->get_parent(); + for (int j = 0; j < descendent_list.size(); j++) { + TreeItem *descendent = descendent_list[j]; + if (visible_as_descendant.has(descendent)) { + continue; // Skip subtree if already processed + } + visible_as_descendant.insert(descendent); - // 1. mark all parents visible - while (parent_of_match) { - visible_items.insert(parent_of_match); - parent_of_match = parent_of_match->get_parent(); - } - - // for 2. and 3. we collect all children of match. - Vector matching_entries_descendents = { cur_item }; - - // 2. and 3. mark all descendents visible - for (int i = 0; i < matching_entries_descendents.size(); i++) { - TreeItem *cur_descendent_item = matching_entries_descendents[i]; - visible_items.insert(cur_descendent_item); - - // 3. collect all children of descendent - for (int i = 0; i < cur_descendent_item->get_child_count(); i++) { - matching_entries_descendents.push_back(cur_descendent_item->get_child(i)); - } - } - - } else { // not a matching entry: process children - for (int i = 0; i < cur_item->get_child_count(); i++) { - items.push_back(cur_item->get_child(i)); + // Collect X-level descendents (bit clunky because godot doesn't return get_children as pointers) + for (int k = 0; k < descendent->get_child_count(); k++) { + TreeItem *child = descendent->get_child(k); + descendent_list.push_back(child); } } } + // Set visibility based on whether items are in either visibility set for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *cur_item = ordered_tree_items[i]; - if (!visible_items.has(cur_item)) { - cur_item->set_visible(false); - } + TreeItem *item = ordered_tree_items[i]; + bool is_visible = visible_as_parent.has(item) || visible_as_descendant.has(item); + item->set_visible(is_visible); } } From 709ecafb83da86b6e52855a01059ebb7c272c6e0 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 23:15:57 +0200 Subject: [PATCH 43/45] Use recursion in _filter_tree for readability --- editor/tree_search.cpp | 65 ++++++++++-------------------------------- editor/tree_search.h | 5 ++-- 2 files changed, 18 insertions(+), 52 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 18fae15..8b3d0db 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -57,56 +57,21 @@ void TreeSearch::_clean_callable_cache() { callable_cache = new_callable_cache; } -// Makes all tree items invisible that don't match the following criteria: -// 1. is a matching tree_item -// 2. is a parent of a matching tree_item -// 3. is any descendant of a matching tree_item -void TreeSearch::_filter_tree(const String &p_search_mask) { - if (number_matches.size() == 0){ - return; +void TreeSearch::_filter_tree() { + ERR_FAIL_COND(!tree_reference); + _filter_tree(tree_reference->get_root(), false); +} + +void TreeSearch::_filter_tree(TreeItem * p_item, bool p_parent_matching) { + bool child_visibility = true; + bool visible = number_matches.has(p_item) | p_parent_matching; + + if (!visible){ + p_item->set_visible(visible); } - HashSet visible_as_parent; - HashSet visible_as_descendant; - - // Mark matching items and all their ancestors (1, 2) - for (int i = 0; i < matching_entries.size(); i++) { - TreeItem *cur_match = matching_entries[i]; - TreeItem *current = cur_match; - while (current != nullptr && !visible_as_parent.has(current)) { - visible_as_parent.insert(current); - current = current->get_parent(); - } - } - - // Mark matching items and all their descendants (1, 3) - for (int i = 0; i < matching_entries.size(); i++) { - TreeItem *cur_match = matching_entries[i]; - if (visible_as_descendant.has(cur_match)) { - continue; - } - // Descendents - Vector descendent_list = { cur_match }; - - for (int j = 0; j < descendent_list.size(); j++) { - TreeItem *descendent = descendent_list[j]; - if (visible_as_descendant.has(descendent)) { - continue; // Skip subtree if already processed - } - visible_as_descendant.insert(descendent); - - // Collect X-level descendents (bit clunky because godot doesn't return get_children as pointers) - for (int k = 0; k < descendent->get_child_count(); k++) { - TreeItem *child = descendent->get_child(k); - descendent_list.push_back(child); - } - } - } - - // Set visibility based on whether items are in either visibility set - for (int i = 0; i < ordered_tree_items.size(); i++) { - TreeItem *item = ordered_tree_items[i]; - bool is_visible = visible_as_parent.has(item) || visible_as_descendant.has(item); - item->set_visible(is_visible); + bool is_matching = _vector_has_bsearch(matching_entries, p_item); + for (int i = 0; i < p_item->get_child_count(); i++) { + _filter_tree(p_item->get_child(i), is_matching | p_parent_matching); } } @@ -468,7 +433,7 @@ void TreeSearch::update_search(Tree *p_tree) { _clear_filter(); _highlight_tree(); if (search_mode == TreeSearchMode::FILTER) { - _filter_tree(search_mask); + _filter_tree(); was_filtered_recently = true; } else if (was_filtered_recently) { _clear_filter(); diff --git a/editor/tree_search.h b/editor/tree_search.h index 0501514..fec49e1 100644 --- a/editor/tree_search.h +++ b/editor/tree_search.h @@ -69,7 +69,8 @@ private: void _clean_callable_cache(); // Update_search() calls these - void _filter_tree(const String &p_search_mask); + void _filter_tree(); + void _filter_tree(TreeItem *item, bool p_parent_matching); void _clear_filter(); void _highlight_tree(); @@ -147,4 +148,4 @@ public: }; #endif // TREE_SEARCH_H -#endif // ! TOOLS_ENABLED +#endif // ! TOOLS_ENABLED \ No newline at end of file From c38e66d0088913d9753588bfa988716be35ebf2f Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 23:16:50 +0200 Subject: [PATCH 44/45] Remove unused include --- editor/tree_search.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 8b3d0db..6ea4a31 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -19,7 +19,6 @@ #ifdef LIMBOAI_MODULE #include "core/math/math_funcs.h" -#include "core/templates/hash_set.h" #include "editor/editor_interface.h" #include "editor/themes/editor_scale.h" #include "scene/gui/separator.h" @@ -35,7 +34,6 @@ #include #include #include -#include #endif // LIMBOAI_GDEXTENSION From b53e470c605ab114894224479d94df0ae95fe6a3 Mon Sep 17 00:00:00 2001 From: Alexander Montag Date: Tue, 1 Oct 2024 23:22:32 +0200 Subject: [PATCH 45/45] Remove unused variable, clang-format --- editor/tree_search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/editor/tree_search.cpp b/editor/tree_search.cpp index 6ea4a31..d9cf2ef 100644 --- a/editor/tree_search.cpp +++ b/editor/tree_search.cpp @@ -60,11 +60,10 @@ void TreeSearch::_filter_tree() { _filter_tree(tree_reference->get_root(), false); } -void TreeSearch::_filter_tree(TreeItem * p_item, bool p_parent_matching) { - bool child_visibility = true; +void TreeSearch::_filter_tree(TreeItem *p_item, bool p_parent_matching) { bool visible = number_matches.has(p_item) | p_parent_matching; - if (!visible){ + if (!visible) { p_item->set_visible(visible); } bool is_matching = _vector_has_bsearch(matching_entries, p_item);