diff --git a/editor/task_palette.cpp b/editor/task_palette.cpp index 387a857..fa4ae56 100644 --- a/editor/task_palette.cpp +++ b/editor/task_palette.cpp @@ -20,6 +20,7 @@ #include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/plugins/script_editor_plugin.h" +#include "scene/gui/check_box.h" //**** TaskButton @@ -198,6 +199,112 @@ void TaskPalette::_apply_filter(const String &p_text) { } } +void TaskPalette::_show_filter_popup() { + _update_filter_popup(); + + category_list->reset_size(); + category_scroll->set_custom_minimum_size(category_list->get_size() + Size2(8, 8)); + + Rect2i rect = tool_filters->get_screen_rect(); + rect.position.y += rect.size.height; + rect.size.height = 0; + filter_popup->reset_size(); + filter_popup->popup(rect); +} + +void TaskPalette::_update_filter_popup() { + switch (filter_settings.type_filter) { + case FilterSettings::TypeFilter::TYPE_ALL: { + type_all->set_pressed(true); + } break; + case FilterSettings::TypeFilter::TYPE_CORE: { + type_core->set_pressed(true); + } break; + case FilterSettings::TypeFilter::TYPE_USER: { + type_user->set_pressed(true); + } break; + } + + switch (filter_settings.category_filter) { + case FilterSettings::CategoryFilter::CATEGORY_ALL: { + category_all->set_pressed(true); + } break; + case FilterSettings::CategoryFilter::CATEGORY_INCLUDE: { + category_include->set_pressed(true); + } break; + case FilterSettings::CategoryFilter::CATEGORY_EXCLUDE: { + category_exclude->set_pressed(true); + } break; + } + + for (int i = 0; i < category_list->get_child_count(); i++) { + category_list->get_child(i)->queue_free(); + } + for (String &cat : LimboTaskDB::get_categories()) { + CheckBox *category_item = memnew(CheckBox); + category_item->set_text(cat); + category_item->set_pressed_no_signal(LOGICAL_XOR( + filter_settings.excluded_categories.has(cat), + filter_settings.category_filter == FilterSettings::CategoryFilter::CATEGORY_INCLUDE)); + category_item->connect(SNAME("toggled"), callable_mp(this, &TaskPalette::_category_item_toggled).bind(cat)); + category_list->add_child(category_item); + } + + category_choice->set_visible(filter_settings.category_filter != FilterSettings::CATEGORY_ALL); +} + +void TaskPalette::_category_filter_changed() { + if (category_all->is_pressed()) { + filter_settings.category_filter = FilterSettings::CategoryFilter::CATEGORY_ALL; + } else if (category_include->is_pressed()) { + filter_settings.category_filter = FilterSettings::CategoryFilter::CATEGORY_INCLUDE; + } else if (category_exclude->is_pressed()) { + filter_settings.category_filter = FilterSettings::CategoryFilter::CATEGORY_EXCLUDE; + } + + for (int i = 0; i < category_list->get_child_count(); i++) { + CheckBox *item = Object::cast_to(category_list->get_child(i)); + item->set_pressed_no_signal(LOGICAL_XOR( + filter_settings.excluded_categories.has(item->get_text()), + filter_settings.category_filter == FilterSettings::CATEGORY_INCLUDE)); + } + + category_choice->set_visible(filter_settings.category_filter != FilterSettings::CATEGORY_ALL); + filter_popup->reset_size(); + _filter_data_changed(); +} + +void TaskPalette::_set_all_filter_categories(bool p_selected) { + for (int i = 0; i < category_list->get_child_count(); i++) { + CheckBox *item = Object::cast_to(category_list->get_child(i)); + item->set_pressed_no_signal(p_selected); + bool excluded = LOGICAL_XOR(p_selected, filter_settings.category_filter == FilterSettings::CATEGORY_INCLUDE); + _set_category_excluded(item->get_text(), excluded); + } + _filter_data_changed(); +} + +void TaskPalette::_type_filter_changed() { + if (type_all->is_pressed()) { + filter_settings.type_filter = FilterSettings::TypeFilter::TYPE_ALL; + } else if (type_core->is_pressed()) { + filter_settings.type_filter = FilterSettings::TypeFilter::TYPE_CORE; + } else if (type_user->is_pressed()) { + filter_settings.type_filter = FilterSettings::TypeFilter::TYPE_USER; + } + _filter_data_changed(); +} + +void TaskPalette::_category_item_toggled(bool p_pressed, const String &p_category) { + bool excluded = LOGICAL_XOR(p_pressed, filter_settings.category_filter == FilterSettings::CATEGORY_INCLUDE); + _set_category_excluded(p_category, excluded); + _filter_data_changed(); +} + +void TaskPalette::_filter_data_changed() { + call_deferred(SNAME("refresh")); +} + void TaskPalette::refresh() { filter_edit->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); @@ -232,8 +339,11 @@ void TaskPalette::refresh() { categories.sort(); for (String cat : categories) { - List tasks = LimboTaskDB::get_tasks_in_category(cat); + if (filter_settings.category_filter != FilterSettings::CATEGORY_ALL && filter_settings.excluded_categories.has(cat)) { + continue; + } + List tasks = LimboTaskDB::get_tasks_in_category(cat); if (tasks.size() == 0) { continue; } @@ -246,12 +356,18 @@ void TaskPalette::refresh() { DocTools *dd = EditorHelp::get_doc_data(); HashMap::Iterator E; if (task_meta.begins_with("res:")) { + if (filter_settings.type_filter == FilterSettings::TYPE_CORE) { + continue; + } tname = task_meta.get_file().get_basename().trim_prefix("BT").to_pascal_case(); E = dd->class_list.find(vformat("\"%s\"", task_meta.trim_prefix("res://"))); if (!E) { E = dd->class_list.find(tname); } } else { + if (filter_settings.type_filter == FilterSettings::TYPE_USER) { + continue; + } tname = task_meta.trim_prefix("BT"); E = dd->class_list.find(task_meta); } @@ -299,7 +415,11 @@ void TaskPalette::_notification(int p_what) { conf.save(conf_path); } break; case NOTIFICATION_THEME_CHANGED: { - refresh_btn->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + tool_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); + tool_refresh->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + select_all->set_icon(get_theme_icon(SNAME("LimboSelectAll"), SNAME("EditorIcons"))); + deselect_all->set_icon(get_theme_icon(SNAME("LimboDeselectAll"), SNAME("EditorIcons"))); + if (is_visible_in_tree()) { refresh(); } @@ -321,6 +441,13 @@ TaskPalette::TaskPalette() { HBoxContainer *hb = memnew(HBoxContainer); vb->add_child(hb); + tool_filters = memnew(Button); + tool_filters->set_tooltip_text(TTR("Show filters")); + tool_filters->set_flat(true); + tool_filters->set_focus_mode(FocusMode::FOCUS_NONE); + tool_filters->connect("pressed", callable_mp(this, &TaskPalette::_show_filter_popup)); + hb->add_child(tool_filters); + filter_edit = memnew(LineEdit); filter_edit->set_clear_button_enabled(true); filter_edit->set_placeholder(TTR("Filter tasks")); @@ -328,12 +455,12 @@ TaskPalette::TaskPalette() { filter_edit->set_h_size_flags(SIZE_EXPAND_FILL); hb->add_child(filter_edit); - refresh_btn = memnew(Button); - refresh_btn->set_tooltip_text(TTR("Refresh tasks")); - refresh_btn->set_flat(true); - refresh_btn->set_focus_mode(FocusMode::FOCUS_NONE); - refresh_btn->connect("pressed", callable_mp(this, &TaskPalette::refresh)); - hb->add_child(refresh_btn); + tool_refresh = memnew(Button); + tool_refresh->set_tooltip_text(TTR("Refresh tasks")); + tool_refresh->set_flat(true); + tool_refresh->set_focus_mode(FocusMode::FOCUS_NONE); + tool_refresh->connect("pressed", callable_mp(this, &TaskPalette::refresh)); + hb->add_child(tool_refresh); ScrollContainer *sc = memnew(ScrollContainer); sc->set_h_size_flags(SIZE_EXPAND_FILL); @@ -348,7 +475,121 @@ TaskPalette::TaskPalette() { menu = memnew(PopupMenu); add_child(menu); menu->connect("id_pressed", callable_mp(this, &TaskPalette::_menu_action_selected)); + + filter_popup = memnew(PopupPanel); + { + VBoxContainer *vbox = memnew(VBoxContainer); + filter_popup->add_child(vbox); + + Label *type_header = memnew(Label); + type_header->set_text(TTR("Type")); + type_header->set_theme_type_variation("HeaderSmall"); + vbox->add_child(type_header); + + HBoxContainer *type_filter = memnew(HBoxContainer); + vbox->add_child(type_filter); + + Ref type_filter_group; + type_filter_group.instantiate(); + + type_all = memnew(Button); + type_all->set_text(TTR("All")); + type_all->set_tooltip_text(TTR("Show tasks of all types")); + type_all->set_toggle_mode(true); + type_all->set_focus_mode(FocusMode::FOCUS_NONE); + type_all->set_button_group(type_filter_group); + type_all->set_pressed(true); + type_all->connect("pressed", callable_mp(this, &TaskPalette::_type_filter_changed)); + type_filter->add_child(type_all); + + type_core = memnew(Button); + type_core->set_text(TTR("Core")); + type_core->set_tooltip_text(TTR("Show only core tasks")); + type_core->set_toggle_mode(true); + type_core->set_focus_mode(FocusMode::FOCUS_NONE); + type_core->set_button_group(type_filter_group); + type_core->connect("pressed", callable_mp(this, &TaskPalette::_type_filter_changed)); + type_filter->add_child(type_core); + + type_user = memnew(Button); + type_user->set_text(TTR("User")); + type_user->set_tooltip_text(TTR("Show only user-implemented tasks (aka scripts)")); + type_user->set_toggle_mode(true); + type_user->set_focus_mode(FocusMode::FOCUS_NONE); + type_user->set_button_group(type_filter_group); + type_user->connect("pressed", callable_mp(this, &TaskPalette::_type_filter_changed)); + type_filter->add_child(type_user); + + Control *space1 = memnew(Control); + space1->set_custom_minimum_size(Size2(0, 4)); + vbox->add_child(space1); + + Label *category_header = memnew(Label); + category_header->set_text(TTR("Categories")); + category_header->set_theme_type_variation("HeaderSmall"); + vbox->add_child(category_header); + + HBoxContainer *category_filter = memnew(HBoxContainer); + vbox->add_child(category_filter); + + Ref category_filter_group; + category_filter_group.instantiate(); + + category_all = memnew(Button); + category_all->set_text(TTR("All")); + category_all->set_tooltip_text(TTR("Show tasks of all categories")); + category_all->set_toggle_mode(true); + category_all->set_focus_mode(FocusMode::FOCUS_NONE); + category_all->set_pressed(true); + category_all->set_button_group(category_filter_group); + category_all->connect("pressed", callable_mp(this, &TaskPalette::_category_filter_changed)); + category_filter->add_child(category_all); + + category_include = memnew(Button); + category_include->set_text(TTR("Include")); + category_include->set_tooltip_text(TTR("Show tasks from selected categories")); + category_include->set_toggle_mode(true); + category_include->set_focus_mode(FocusMode::FOCUS_NONE); + category_include->set_button_group(category_filter_group); + category_include->connect("pressed", callable_mp(this, &TaskPalette::_category_filter_changed)); + category_filter->add_child(category_include); + + category_exclude = memnew(Button); + category_exclude->set_text(TTR("Exclude")); + category_exclude->set_tooltip_text(TTR("Don't show tasks from selected categories")); + category_exclude->set_toggle_mode(true); + category_exclude->set_focus_mode(FocusMode::FOCUS_NONE); + category_exclude->set_button_group(category_filter_group); + category_exclude->connect("pressed", callable_mp(this, &TaskPalette::_category_filter_changed)); + category_filter->add_child(category_exclude); + + category_choice = memnew(VBoxContainer); + vbox->add_child(category_choice); + + HBoxContainer *selection_controls = memnew(HBoxContainer); + selection_controls->set_alignment(BoxContainer::ALIGNMENT_CENTER); + category_choice->add_child(selection_controls); + + select_all = memnew(Button); + select_all->set_tooltip_text(TTR("Select all categories")); + select_all->set_focus_mode(FocusMode::FOCUS_NONE); + select_all->connect("pressed", callable_mp(this, &TaskPalette::_set_all_filter_categories).bind(true)); + selection_controls->add_child(select_all); + + deselect_all = memnew(Button); + deselect_all->set_tooltip_text(TTR("Deselect all categories")); + deselect_all->set_focus_mode(FocusMode::FOCUS_NONE); + deselect_all->connect("pressed", callable_mp(this, &TaskPalette::_set_all_filter_categories).bind(false)); + selection_controls->add_child(deselect_all); + + category_scroll = memnew(ScrollContainer); + category_choice->add_child(category_scroll); + + category_list = memnew(VBoxContainer); + category_scroll->add_child(category_list); + } + add_child(filter_popup); } TaskPalette::~TaskPalette() { -} \ No newline at end of file +} diff --git a/editor/task_palette.h b/editor/task_palette.h index d058035..e9ae5c0 100644 --- a/editor/task_palette.h +++ b/editor/task_palette.h @@ -15,8 +15,10 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/check_box.h" #include "scene/gui/flow_container.h" #include "scene/gui/line_edit.h" +#include "scene/gui/popup.h" class TaskButton : public Button { GDCLASS(TaskButton, Button); @@ -64,10 +66,41 @@ private: MENU_FAVORITE, }; + struct FilterSettings { + enum TypeFilter { + TYPE_ALL, + TYPE_CORE, + TYPE_USER, + } type_filter; + + enum CategoryFilter { + CATEGORY_ALL, + CATEGORY_INCLUDE, + CATEGORY_EXCLUDE, + } category_filter; + + HashSet excluded_categories; + } filter_settings; + LineEdit *filter_edit; VBoxContainer *sections; PopupMenu *menu; - Button *refresh_btn; + Button *tool_filters; + Button *tool_refresh; + + // Filter popup + PopupPanel *filter_popup; + Button *type_all; + Button *type_core; + Button *type_user; + Button *category_all; + Button *category_include; + Button *category_exclude; + VBoxContainer *category_choice; + Button *select_all; + Button *deselect_all; + ScrollContainer *category_scroll; + VBoxContainer *category_list; String context_task; @@ -75,6 +108,21 @@ private: void _on_task_button_pressed(const String &p_task); void _on_task_button_rmb(const String &p_task); void _apply_filter(const String &p_text); + void _show_filter_popup(); + void _update_filter_popup(); + void _type_filter_changed(); + void _category_filter_changed(); + void _set_all_filter_categories(bool p_selected); + void _category_item_toggled(bool p_pressed, const String &p_category); + void _filter_data_changed(); + + _FORCE_INLINE_ void _set_category_excluded(const String &p_category, bool p_excluded) { + if (p_excluded) { + filter_settings.excluded_categories.insert(p_category); + } else { + filter_settings.excluded_categories.erase(p_category); + } + } protected: static void _bind_methods(); diff --git a/icons/LimboDeselectAll.svg b/icons/LimboDeselectAll.svg new file mode 100644 index 0000000..2047080 --- /dev/null +++ b/icons/LimboDeselectAll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/LimboSelectAll.svg b/icons/LimboSelectAll.svg new file mode 100644 index 0000000..fd3df37 --- /dev/null +++ b/icons/LimboSelectAll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/util/limbo_task_db.cpp b/util/limbo_task_db.cpp index 6fc4c08..955e8d7 100644 --- a/util/limbo_task_db.cpp +++ b/util/limbo_task_db.cpp @@ -94,6 +94,7 @@ List LimboTaskDB::get_categories() { for (const KeyValue> &E : tasks_cache) { r_cat.push_back(E.key); } + r_cat.sort(); return r_cat; } diff --git a/util/limbo_utility.h b/util/limbo_utility.h index a102a0c..036e3dc 100644 --- a/util/limbo_utility.h +++ b/util/limbo_utility.h @@ -18,6 +18,8 @@ #include "core/variant/variant.h" #include "scene/resources/texture.h" +#define LOGICAL_XOR(a, b) (a) ? !(b) : (b) + class LimboUtility : public Object { GDCLASS(LimboUtility, Object);