diff --git a/editor/gui/tree_sort_and_filter_line_edit.cpp b/editor/gui/tree_sort_and_filter_line_edit.cpp new file mode 100644 index 000000000000..57fe15203dcc --- /dev/null +++ b/editor/gui/tree_sort_and_filter_line_edit.cpp @@ -0,0 +1,226 @@ +/**************************************************************************/ +/* tree_sort_and_filter_line_edit.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "tree_sort_and_filter_line_edit.h" + +#include "core/object/callable_mp.h" +#include "editor/editor_node.h" +#include "editor/editor_string_names.h" +#include "editor/themes/editor_scale.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/menu_button.h" + +void TreeSortAndFilterBar::_apply_filter(TreeItem *p_current_node) { + if (!p_current_node) { + p_current_node = managed_tree->get_root(); + } + + if (!p_current_node) { + return; + } + + // Reset ourselves to default state. + p_current_node->set_visible(true); + p_current_node->clear_custom_color(0); + + // Go through each child and filter them. + bool any_child_visible = false; + for (TreeItem *child = p_current_node->get_first_child(); child; child = child->get_next()) { + _apply_filter(child); + if (child->is_visible()) { + any_child_visible = true; + } + } + + // Check if we match the filter. + String filter_str = filter_edit->get_text().strip_edges(true, true).to_lower(); + + // We are visible. + bool matches_filter = false; + for (int i = 0; i < managed_tree->get_columns(); i++) { + if (p_current_node->get_text(i).to_lower().contains(filter_str)) { + matches_filter = true; + break; + } + } + if (matches_filter || filter_str.is_empty()) { + p_current_node->set_visible(true); + } else if (any_child_visible) { + // We have a visible child. + p_current_node->set_custom_color(0, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))); + } else { + // We and our children are not visible. + p_current_node->set_visible(false); + } +} + +void TreeSortAndFilterBar::_apply_sort() { + if (!sort_button->is_visible()) { + return; + } + for (int i = 0; i != sort_button->get_popup()->get_item_count(); i++) { + // Update the popup buttons to be checked/unchecked. + sort_button->get_popup()->set_item_checked(i, (i == (int)current_sort)); + } + + SortItem sort = sort_items[current_sort]; + + List items_to_sort; + items_to_sort.push_back(managed_tree->get_root()); + + while (items_to_sort.size() > 0) { + TreeItem *to_sort = items_to_sort.front()->get(); + items_to_sort.pop_front(); + + LocalVector items; + items.reserve(to_sort->get_child_count()); + for (int i = 0; i < to_sort->get_child_count(); i++) { + items.push_back(TreeItemColumn(to_sort->get_child(i), sort.column)); + } + + if (sort.type == ALPHA_SORT && sort.ascending == true) { + items.sort_custom(); + } + if (sort.type == ALPHA_SORT && sort.ascending == false) { + items.sort_custom(); + items.reverse(); + } + if (sort.type == NUMERIC_SORT && sort.ascending == true) { + items.sort_custom(); + } + if (sort.type == NUMERIC_SORT && sort.ascending == false) { + items.sort_custom(); + items.reverse(); + } + + TreeItem *previous = nullptr; + for (const TreeItemColumn &item : items) { + if (previous != nullptr) { + item.item->move_after(previous); + } else { + item.item->move_before(to_sort->get_first_child()); + } + previous = item.item; + items_to_sort.push_back(item.item); + } + } +} + +void TreeSortAndFilterBar::_sort_changed(int p_id) { + current_sort = p_id; + _apply_sort(); +} + +void TreeSortAndFilterBar::_filter_changed(const String &p_filter) { + _apply_filter(); +} + +TreeSortAndFilterBar::TreeSortAndFilterBar(Tree *p_managed_tree, const String &p_filter_placeholder_text) : + managed_tree(p_managed_tree) { + set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL); + add_theme_constant_override("h_separation", 10 * EDSCALE); + filter_edit = memnew(LineEdit); + filter_edit->set_clear_button_enabled(true); + filter_edit->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL); + filter_edit->set_placeholder(p_filter_placeholder_text); + add_child(filter_edit); + filter_edit->connect(SceneStringName(text_changed), callable_mp(this, &TreeSortAndFilterBar::_filter_changed)); + + sort_button = memnew(MenuButton); + sort_button->set_visible(false); + sort_button->set_flat(false); + sort_button->set_theme_type_variation("FlatMenuButton"); + PopupMenu *p = sort_button->get_popup(); + p->connect(SceneStringName(id_pressed), callable_mp(this, &TreeSortAndFilterBar::_sort_changed)); + + add_child(sort_button); +} + +void TreeSortAndFilterBar::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + filter_edit->set_right_icon(get_editor_theme_icon(SNAME("Search"))); + sort_button->set_button_icon(get_editor_theme_icon(SNAME("Sort"))); + apply(); + } break; + } +} + +TreeSortAndFilterBar::SortOptionIndexes TreeSortAndFilterBar::add_sort_option(const String &p_new_option, SortType p_sort_type, int p_sort_column, bool p_is_default) { + sort_button->set_visible(true); + bool is_first_item = sort_items.is_empty(); + SortItem item_ascending(sort_items.size(), vformat(TTR("Sort By %s (Ascending)"), p_new_option), p_sort_type, true, p_sort_column); + sort_items[item_ascending.id] = item_ascending; + sort_button->get_popup()->add_radio_check_item(item_ascending.label, item_ascending.id); + + SortItem item_descending(sort_items.size(), vformat(TTR("Sort By %s (Descending)"), p_new_option), p_sort_type, false, p_sort_column); + sort_items[item_descending.id] = item_descending; + sort_button->get_popup()->add_radio_check_item(item_descending.label, item_descending.id); + + if (is_first_item) { + sort_button->get_popup()->set_item_checked(0, true); + } + + SortOptionIndexes indexes; + indexes.ascending = item_ascending.id; + indexes.descending = item_descending.id; + return indexes; +} + +void TreeSortAndFilterBar::clear_filter() { + filter_edit->clear(); +} + +void TreeSortAndFilterBar::clear() { + sort_button->set_visible(false); + sort_button->get_popup()->clear(); + filter_edit->clear(); +} + +void TreeSortAndFilterBar::select_sort(int p_item_id) { + _sort_changed(p_item_id); +} + +void TreeSortAndFilterBar::apply() { + if (!managed_tree || !managed_tree->get_root()) { + return; + } + + _apply_sort(); + _apply_filter(); +} + +void TreeSortAndFilterBar::set_tree(Tree *p_managed_tree) { + if (managed_tree == p_managed_tree) { + return; + } + managed_tree = p_managed_tree; + apply(); +} diff --git a/editor/gui/tree_sort_and_filter_line_edit.h b/editor/gui/tree_sort_and_filter_line_edit.h new file mode 100644 index 000000000000..3d66a4c06ca8 --- /dev/null +++ b/editor/gui/tree_sort_and_filter_line_edit.h @@ -0,0 +1,111 @@ +/**************************************************************************/ +/* tree_sort_and_filter_line_edit.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "scene/gui/box_container.h" +#include "scene/gui/tree.h" + +class LineEdit; +class MenuButton; + +class TreeSortAndFilterBar : public HBoxContainer { + GDCLASS(TreeSortAndFilterBar, HBoxContainer); + +public: + // The ways a column can be sorted, either alphabetically or numerically. + enum SortType { + NUMERIC_SORT = 0, + ALPHA_SORT, + SORT_TYPE_MAX + }; + + // Returned when a new sort is added. Each new sort can be either ascending or descending, + // so we return the index of each sort option. + struct SortOptionIndexes { + int ascending; + int descending; + }; + +protected: + // Context needed to sort the tree in a certain way. + // Combines a sort type, the column to apply it, and if it's ascending or descending. + struct SortItem { + SortItem() {} + SortItem(int p_id, const String &p_label, SortType p_type, bool p_ascending, int p_column) : + id(p_id), label(p_label), type(p_type), ascending(p_ascending), column(p_column) {} + int id = 0; + String label; + SortType type = SortType::NUMERIC_SORT; + bool ascending = false; + int column = 0; + }; + + struct TreeItemColumn { + TreeItemColumn() {} + TreeItemColumn(TreeItem *p_item, int p_column) : + item(p_item), column(p_column) {} + TreeItem *item = nullptr; + int column; + }; + + struct TreeItemAlphaComparator { + bool operator()(const TreeItemColumn &p_a, const TreeItemColumn &p_b) const { + return NoCaseComparator()(p_a.item->get_text(p_a.column), p_b.item->get_text(p_b.column)); + } + }; + + struct TreeItemNumericComparator { + bool operator()(const TreeItemColumn &p_a, const TreeItemColumn &p_b) const { + return p_a.item->get_text(p_a.column).to_int() < p_b.item->get_text(p_b.column).to_int(); + } + }; + + Tree *managed_tree = nullptr; + LineEdit *filter_edit = nullptr; + MenuButton *sort_button = nullptr; + HashMap sort_items; + int current_sort = 0; + + void _apply_filter(TreeItem *p_current_node = nullptr); + void _apply_sort(); + void _sort_changed(int p_id); + void _filter_changed(const String &p_filter); + +public: + TreeSortAndFilterBar(Tree *p_managed_tree, const String &p_filter_placeholder_text); + void _notification(int p_what); + void set_tree(Tree *p_managed_tree); + SortOptionIndexes add_sort_option(const String &p_new_option, SortType p_sort_type, int p_sort_column, bool p_is_default = false); + void clear_filter(); + void clear(); + void select_sort(int p_item_id); + void apply(); +}; diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 53bfb6115f2f..77eef9f46c91 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -36,6 +36,7 @@ #include "editor/editor_string_names.h" #include "editor/file_system/editor_file_system.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/gui/tree_sort_and_filter_line_edit.h" #include "editor/inspector/editor_inspector.h" #include "editor/scene/3d/skeleton_3d_editor_plugin.h" #include "editor/settings/editor_settings.h" @@ -747,9 +748,8 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, const String const bool disable_mesh_mat_tabs = p_scene_import_type == "AnimationLibrary"; data_mode->set_tab_hidden(1, disable_mesh_mat_tabs); data_mode->set_tab_hidden(2, disable_mesh_mat_tabs); - if (disable_mesh_mat_tabs) { - data_mode->set_current_tab(0); - } + data_mode->set_current_tab(0); + _on_tree_tab_changed(0); // Only show the save data options for PackedScene imports of scenes, not resource imports. const bool disable_save_mesh_mat = p_scene_import_type != "PackedScene"; @@ -1235,6 +1235,11 @@ void SceneImportSettingsDialog::_on_light_rotate_switch_pressed() { light2->set_as_top_level_keep_local(light_top_level); } +void SceneImportSettingsDialog::_on_tree_tab_changed(int p_tab_id) { + tree_filter_bar->set_tree(static_cast(data_mode->get_tab_control(p_tab_id))); + tree_filter_bar->clear(); +} + void SceneImportSettingsDialog::_viewport_input(const Ref &p_input) { float *rot_x = &cam_rot_x; float *rot_y = &cam_rot_y; @@ -1711,29 +1716,39 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { main_vb->add_child(tree_split); tree_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + VBoxContainer *data_mode_vbc = memnew(VBoxContainer); + tree_split->add_child(data_mode_vbc); + data_mode_vbc->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT); + + scene_tree = memnew(Tree); + mesh_tree = memnew(Tree); + material_tree = memnew(Tree); + + tree_filter_bar = memnew(TreeSortAndFilterBar(scene_tree, TTRC("Filter"))); + data_mode_vbc->add_child(tree_filter_bar); + data_mode = memnew(TabContainer); - tree_split->add_child(data_mode); + data_mode_vbc->add_child(data_mode); data_mode->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); data_mode->set_theme_type_variation("TabContainerOdd"); + data_mode->set_v_size_flags(Control::SIZE_EXPAND_FILL); + data_mode->connect("tab_changed", callable_mp(this, &SceneImportSettingsDialog::_on_tree_tab_changed)); property_split = memnew(HSplitContainer); tree_split->add_child(property_split); property_split->set_h_size_flags(Control::SIZE_EXPAND_FILL); - scene_tree = memnew(Tree); scene_tree->set_name(TTR("Scene")); scene_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); data_mode->add_child(scene_tree); scene_tree->connect("cell_selected", callable_mp(this, &SceneImportSettingsDialog::_scene_tree_selected)); - mesh_tree = memnew(Tree); mesh_tree->set_name(TTR("Meshes")); mesh_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); data_mode->add_child(mesh_tree); mesh_tree->set_hide_root(true); mesh_tree->connect("cell_selected", callable_mp(this, &SceneImportSettingsDialog::_mesh_tree_selected)); - material_tree = memnew(Tree); material_tree->set_name(TTR("Materials")); material_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); data_mode->add_child(material_tree); diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index 842c5c1c9418..efd3db2d6c41 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -50,6 +50,7 @@ class EditorFileDialog; class EditorInspector; class SceneImportSettingsData; class Timer; +class TreeSortAndFilterBar; class SceneImportSettingsDialog : public ConfirmationDialog { GDCLASS(SceneImportSettingsDialog, ConfirmationDialog) @@ -71,6 +72,8 @@ class SceneImportSettingsDialog : public ConfirmationDialog { Tree *mesh_tree = nullptr; Tree *material_tree = nullptr; + TreeSortAndFilterBar *tree_filter_bar = nullptr; + EditorInspector *inspector = nullptr; SubViewport *base_viewport = nullptr; @@ -198,6 +201,7 @@ class SceneImportSettingsDialog : public ConfirmationDialog { void _on_light_1_switch_pressed(); void _on_light_2_switch_pressed(); void _on_light_rotate_switch_pressed(); + void _on_tree_tab_changed(int p_tab_id); void _viewport_input(const Ref &p_input); diff --git a/modules/objectdb_profiler/editor/data_viewers/class_view.cpp b/modules/objectdb_profiler/editor/data_viewers/class_view.cpp index f43466698644..dbed5f2f130c 100644 --- a/modules/objectdb_profiler/editor/data_viewers/class_view.cpp +++ b/modules/objectdb_profiler/editor/data_viewers/class_view.cpp @@ -30,11 +30,10 @@ #include "class_view.h" -#include "shared_controls.h" - #include "core/object/callable_mp.h" #include "core/object/class_db.h" #include "editor/editor_node.h" +#include "editor/gui/tree_sort_and_filter_line_edit.h" #include "editor/themes/editor_scale.h" #include "scene/gui/split_container.h" diff --git a/modules/objectdb_profiler/editor/data_viewers/node_view.cpp b/modules/objectdb_profiler/editor/data_viewers/node_view.cpp index 119bf9a6094b..438f0f5ed9ff 100644 --- a/modules/objectdb_profiler/editor/data_viewers/node_view.cpp +++ b/modules/objectdb_profiler/editor/data_viewers/node_view.cpp @@ -32,6 +32,7 @@ #include "core/object/callable_mp.h" #include "editor/editor_node.h" +#include "editor/gui/tree_sort_and_filter_line_edit.h" #include "editor/themes/editor_scale.h" #include "scene/gui/check_button.h" #include "scene/gui/popup_menu.h" diff --git a/modules/objectdb_profiler/editor/data_viewers/node_view.h b/modules/objectdb_profiler/editor/data_viewers/node_view.h index cb9540306e80..6589e2a8a3b6 100644 --- a/modules/objectdb_profiler/editor/data_viewers/node_view.h +++ b/modules/objectdb_profiler/editor/data_viewers/node_view.h @@ -31,10 +31,9 @@ #pragma once #include "../snapshot_data.h" -#include "shared_controls.h" #include "snapshot_view.h" -class Tree; +class TreeSortAndFilterBar; // When diffing in split view, we have two trees/filters // so this struct is used to group their properties together. diff --git a/modules/objectdb_profiler/editor/data_viewers/object_view.cpp b/modules/objectdb_profiler/editor/data_viewers/object_view.cpp index 5784a79a46c1..5c39e88912a9 100644 --- a/modules/objectdb_profiler/editor/data_viewers/object_view.cpp +++ b/modules/objectdb_profiler/editor/data_viewers/object_view.cpp @@ -30,8 +30,11 @@ #include "object_view.h" +#include "shared_controls.h" + #include "core/object/callable_mp.h" #include "editor/editor_node.h" +#include "editor/gui/tree_sort_and_filter_line_edit.h" #include "editor/themes/editor_scale.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/split_container.h" diff --git a/modules/objectdb_profiler/editor/data_viewers/object_view.h b/modules/objectdb_profiler/editor/data_viewers/object_view.h index 0cb1921b60fb..c46d2b024eff 100644 --- a/modules/objectdb_profiler/editor/data_viewers/object_view.h +++ b/modules/objectdb_profiler/editor/data_viewers/object_view.h @@ -31,11 +31,10 @@ #pragma once #include "../snapshot_data.h" -#include "shared_controls.h" #include "snapshot_view.h" -class Tree; class HSplitContainer; +class TreeSortAndFilterBar; class SnapshotObjectView : public SnapshotView { GDCLASS(SnapshotObjectView, SnapshotView); diff --git a/modules/objectdb_profiler/editor/data_viewers/refcounted_view.cpp b/modules/objectdb_profiler/editor/data_viewers/refcounted_view.cpp index 8fed31c313a6..33dc3d4035da 100644 --- a/modules/objectdb_profiler/editor/data_viewers/refcounted_view.cpp +++ b/modules/objectdb_profiler/editor/data_viewers/refcounted_view.cpp @@ -30,8 +30,11 @@ #include "refcounted_view.h" +#include "shared_controls.h" + #include "core/object/callable_mp.h" #include "editor/editor_node.h" +#include "editor/gui/tree_sort_and_filter_line_edit.h" #include "editor/themes/editor_scale.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/split_container.h" diff --git a/modules/objectdb_profiler/editor/data_viewers/refcounted_view.h b/modules/objectdb_profiler/editor/data_viewers/refcounted_view.h index 5d110623dc62..60aabf131b4b 100644 --- a/modules/objectdb_profiler/editor/data_viewers/refcounted_view.h +++ b/modules/objectdb_profiler/editor/data_viewers/refcounted_view.h @@ -31,11 +31,10 @@ #pragma once #include "../snapshot_data.h" -#include "shared_controls.h" #include "snapshot_view.h" -class Tree; class HSplitContainer; +class TreeSortAndFilterBar; class SnapshotRefCountedView : public SnapshotView { GDCLASS(SnapshotRefCountedView, SnapshotView); diff --git a/modules/objectdb_profiler/editor/data_viewers/shared_controls.cpp b/modules/objectdb_profiler/editor/data_viewers/shared_controls.cpp index c1c24e29e092..f2f9fe04d290 100644 --- a/modules/objectdb_profiler/editor/data_viewers/shared_controls.cpp +++ b/modules/objectdb_profiler/editor/data_viewers/shared_controls.cpp @@ -30,13 +30,8 @@ #include "shared_controls.h" -#include "core/object/callable_mp.h" #include "editor/editor_node.h" -#include "editor/editor_string_names.h" -#include "editor/themes/editor_scale.h" #include "scene/gui/label.h" -#include "scene/gui/line_edit.h" -#include "scene/gui/menu_button.h" #include "scene/resources/style_box_flat.h" SpanningHeader::SpanningHeader(const String &p_text) { @@ -59,184 +54,3 @@ DarkPanelContainer::DarkPanelContainer() { content_wrapper_sbf->set_bg_color(EditorNode::get_singleton()->get_editor_theme()->get_color("dark_color_2", "Editor")); add_theme_style_override(SceneStringName(panel), content_wrapper_sbf); } - -void TreeSortAndFilterBar::_apply_filter(TreeItem *p_current_node) { - if (!p_current_node) { - p_current_node = managed_tree->get_root(); - } - - if (!p_current_node) { - return; - } - - // Reset ourselves to default state. - p_current_node->set_visible(true); - p_current_node->clear_custom_color(0); - - // Go through each child and filter them. - bool any_child_visible = false; - for (TreeItem *child = p_current_node->get_first_child(); child; child = child->get_next()) { - _apply_filter(child); - if (child->is_visible()) { - any_child_visible = true; - } - } - - // Check if we match the filter. - String filter_str = filter_edit->get_text().strip_edges(true, true).to_lower(); - - // We are visible. - bool matches_filter = false; - for (int i = 0; i < managed_tree->get_columns(); i++) { - if (p_current_node->get_text(i).to_lower().contains(filter_str)) { - matches_filter = true; - break; - } - } - if (matches_filter || filter_str.is_empty()) { - p_current_node->set_visible(true); - } else if (any_child_visible) { - // We have a visible child. - p_current_node->set_custom_color(0, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))); - } else { - // We and our children are not visible. - p_current_node->set_visible(false); - } -} - -void TreeSortAndFilterBar::_apply_sort() { - if (!sort_button->is_visible()) { - return; - } - for (int i = 0; i != sort_button->get_popup()->get_item_count(); i++) { - // Update the popup buttons to be checked/unchecked. - sort_button->get_popup()->set_item_checked(i, (i == (int)current_sort)); - } - - SortItem sort = sort_items[current_sort]; - - List items_to_sort; - items_to_sort.push_back(managed_tree->get_root()); - - while (items_to_sort.size() > 0) { - TreeItem *to_sort = items_to_sort.front()->get(); - items_to_sort.pop_front(); - - LocalVector items; - items.reserve(to_sort->get_child_count()); - for (int i = 0; i < to_sort->get_child_count(); i++) { - items.push_back(TreeItemColumn(to_sort->get_child(i), sort.column)); - } - - if (sort.type == ALPHA_SORT && sort.ascending == true) { - items.sort_custom(); - } - if (sort.type == ALPHA_SORT && sort.ascending == false) { - items.sort_custom(); - items.reverse(); - } - if (sort.type == NUMERIC_SORT && sort.ascending == true) { - items.sort_custom(); - } - if (sort.type == NUMERIC_SORT && sort.ascending == false) { - items.sort_custom(); - items.reverse(); - } - - TreeItem *previous = nullptr; - for (const TreeItemColumn &item : items) { - if (previous != nullptr) { - item.item->move_after(previous); - } else { - item.item->move_before(to_sort->get_first_child()); - } - previous = item.item; - items_to_sort.push_back(item.item); - } - } -} - -void TreeSortAndFilterBar::_sort_changed(int p_id) { - current_sort = p_id; - _apply_sort(); -} - -void TreeSortAndFilterBar::_filter_changed(const String &p_filter) { - _apply_filter(); -} - -TreeSortAndFilterBar::TreeSortAndFilterBar(Tree *p_managed_tree, const String &p_filter_placeholder_text) : - managed_tree(p_managed_tree) { - set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL); - add_theme_constant_override("h_separation", 10 * EDSCALE); - filter_edit = memnew(LineEdit); - filter_edit->set_clear_button_enabled(true); - filter_edit->set_h_size_flags(SizeFlags::SIZE_EXPAND_FILL); - filter_edit->set_placeholder(p_filter_placeholder_text); - add_child(filter_edit); - filter_edit->connect(SceneStringName(text_changed), callable_mp(this, &TreeSortAndFilterBar::_filter_changed)); - - sort_button = memnew(MenuButton); - sort_button->set_visible(false); - sort_button->set_flat(false); - sort_button->set_theme_type_variation("FlatMenuButton"); - PopupMenu *p = sort_button->get_popup(); - p->connect(SceneStringName(id_pressed), callable_mp(this, &TreeSortAndFilterBar::_sort_changed)); - - add_child(sort_button); -} - -void TreeSortAndFilterBar::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_POSTINITIALIZE: - case NOTIFICATION_THEME_CHANGED: { - filter_edit->set_right_icon(get_editor_theme_icon(SNAME("Search"))); - sort_button->set_button_icon(get_editor_theme_icon(SNAME("Sort"))); - apply(); - } break; - } -} - -TreeSortAndFilterBar::SortOptionIndexes TreeSortAndFilterBar::add_sort_option(const String &p_new_option, SortType p_sort_type, int p_sort_column, bool p_is_default) { - sort_button->set_visible(true); - bool is_first_item = sort_items.is_empty(); - SortItem item_ascending(sort_items.size(), vformat(TTR("Sort By %s (Ascending)"), p_new_option), p_sort_type, true, p_sort_column); - sort_items[item_ascending.id] = item_ascending; - sort_button->get_popup()->add_radio_check_item(item_ascending.label, item_ascending.id); - - SortItem item_descending(sort_items.size(), vformat(TTR("Sort By %s (Descending)"), p_new_option), p_sort_type, false, p_sort_column); - sort_items[item_descending.id] = item_descending; - sort_button->get_popup()->add_radio_check_item(item_descending.label, item_descending.id); - - if (is_first_item) { - sort_button->get_popup()->set_item_checked(0, true); - } - - SortOptionIndexes indexes; - indexes.ascending = item_ascending.id; - indexes.descending = item_descending.id; - return indexes; -} - -void TreeSortAndFilterBar::clear_filter() { - filter_edit->clear(); -} - -void TreeSortAndFilterBar::clear() { - sort_button->set_visible(false); - sort_button->get_popup()->clear(); - filter_edit->clear(); -} - -void TreeSortAndFilterBar::select_sort(int p_item_id) { - _sort_changed(p_item_id); -} - -void TreeSortAndFilterBar::apply() { - if (!managed_tree || !managed_tree->get_root()) { - return; - } - - _apply_sort(); - _apply_filter(); -} diff --git a/modules/objectdb_profiler/editor/data_viewers/shared_controls.h b/modules/objectdb_profiler/editor/data_viewers/shared_controls.h index bebc1441be22..6fb53c80d4df 100644 --- a/modules/objectdb_profiler/editor/data_viewers/shared_controls.h +++ b/modules/objectdb_profiler/editor/data_viewers/shared_controls.h @@ -30,9 +30,7 @@ #pragma once -#include "scene/gui/box_container.h" #include "scene/gui/panel_container.h" -#include "scene/gui/tree.h" class LineEdit; class MenuButton; @@ -50,78 +48,3 @@ class DarkPanelContainer : public PanelContainer { public: DarkPanelContainer(); }; - -// Utility class that creates a filter text box and a sort menu. -// Takes a reference to a tree and applies the sort and filter to the tree. -class TreeSortAndFilterBar : public HBoxContainer { - GDCLASS(TreeSortAndFilterBar, HBoxContainer); - -public: - // The ways a column can be sorted, either alphabetically or numerically. - enum SortType { - NUMERIC_SORT = 0, - ALPHA_SORT, - SORT_TYPE_MAX - }; - - // Returned when a new sort is added. Each new sort can be either ascending or descending, - // so we return the index of each sort option. - struct SortOptionIndexes { - int ascending; - int descending; - }; - -protected: - // Context needed to sort the tree in a certain way. - // Combines a sort type, the column to apply it, and if it's ascending or descending. - struct SortItem { - SortItem() {} - SortItem(int p_id, const String &p_label, SortType p_type, bool p_ascending, int p_column) : - id(p_id), label(p_label), type(p_type), ascending(p_ascending), column(p_column) {} - int id = 0; - String label; - SortType type = SortType::NUMERIC_SORT; - bool ascending = false; - int column = 0; - }; - - struct TreeItemColumn { - TreeItemColumn() {} - TreeItemColumn(TreeItem *p_item, int p_column) : - item(p_item), column(p_column) {} - TreeItem *item = nullptr; - int column; - }; - - struct TreeItemAlphaComparator { - bool operator()(const TreeItemColumn &p_a, const TreeItemColumn &p_b) const { - return NoCaseComparator()(p_a.item->get_text(p_a.column), p_b.item->get_text(p_b.column)); - } - }; - - struct TreeItemNumericComparator { - bool operator()(const TreeItemColumn &p_a, const TreeItemColumn &p_b) const { - return p_a.item->get_text(p_a.column).to_int() < p_b.item->get_text(p_b.column).to_int(); - } - }; - - LineEdit *filter_edit = nullptr; - MenuButton *sort_button = nullptr; - Tree *managed_tree = nullptr; - HashMap sort_items; - int current_sort = 0; - - void _apply_filter(TreeItem *p_current_node = nullptr); - void _apply_sort(); - void _sort_changed(int p_id); - void _filter_changed(const String &p_filter); - -public: - TreeSortAndFilterBar(Tree *p_managed_tree, const String &p_filter_placeholder_text); - void _notification(int p_what); - SortOptionIndexes add_sort_option(const String &p_new_option, SortType p_sort_type, int p_sort_column, bool p_is_default = false); - void clear_filter(); - void clear(); - void select_sort(int p_item_id); - void apply(); -};