This commit is contained in:
Stijn Hinlopen 2021-11-08 22:31:12 +01:00 committed by GitHub
commit 5946b93e4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 910 additions and 593 deletions

View file

@ -53,34 +53,38 @@
static const int MAX_DECIMALS = 32;
static _FORCE_INLINE_ bool is_digit(char32_t c) {
return (c >= '0' && c <= '9');
}
static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
return (c >= 'A' && c <= 'Z');
}
static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
return (c >= 'a' && c <= 'z');
}
static _FORCE_INLINE_ char32_t lower_case(char32_t c) {
return (is_upper_case(c) ? (c + ('a' - 'A')) : c);
}
const char CharString::_null = 0;
const char16_t Char16String::_null = 0;
const char32_t String::_null = 0;
bool is_digit(char32_t c) {
return (c >= '0' && c <= '9');
}
bool is_hex_digit(char32_t c) {
return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
bool is_upper_case(char32_t c) {
return (c >= 'A' && c <= 'Z');
}
bool is_lower_case(char32_t c) {
return (c >= 'a' && c <= 'z');
}
bool is_alphabetic(char32_t c) {
return is_lower_case(c) || is_upper_case(c);
}
bool is_symbol(char32_t c) {
return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
}
char32_t lower_case(char32_t c) {
return (is_upper_case(c) ? (c + ('a' - 'A')) : c);
}
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) {
const String &s = p_s;
int beg = CLAMP(p_col, 0, s.length());

View file

@ -539,7 +539,15 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
String RTR(const String &p_text, const String &p_context = "");
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
bool is_digit(char32_t c);
bool is_hex_digit(char32_t c);
bool is_upper_case(char32_t c);
bool is_lower_case(char32_t c);
bool is_alphabetic(char32_t c);
bool is_symbol(char32_t c);
char32_t lower_case(char32_t c);
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end);
_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) {

File diff suppressed because it is too large Load diff

View file

@ -38,70 +38,121 @@
#include "scene/gui/line_edit.h"
#include "scene/gui/tree.h"
class CreateDialogCandidate {
String type;
String wb_chars;
bool is_preferred_type = false;
bool in_favorites = false;
bool in_recent = false;
String _compute_word_boundary_characters() const;
float _word_score(const String &p_word, const String &p_query) const;
public:
bool is_valid(const String &p_query) const;
float compute_score(const String &p_query) const;
String get_type() const { return type; }
CreateDialogCandidate() {}
CreateDialogCandidate(const String &p_type, const bool p_is_preferred_type, const bool in_favorites, const bool in_recent);
};
class FavoriteList : public Tree {
GDCLASS(FavoriteList, Tree);
Vector<String> favorites;
String icon_fallback;
bool favorites_changed = false;
Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
void _update_tree();
protected:
void _notification(int p_what) {}
static void _bind_methods();
public:
void load_favorites(const String &p_file_id, const String &p_icon_fallback);
bool toggle_favorite(const String &p_type);
bool has_favorite(const String &p_type) { return favorites.has(p_type); }
bool save_favorites(const String &p_file_id);
FavoriteList();
};
class HistoryList : public ItemList {
GDCLASS(HistoryList, ItemList);
Set<String> history;
public:
void load_history(const String &p_file_id, const String &p_icon_fallback);
void save_to_history(const String &p_file_id, const String &p_item);
bool has_history(const String &p_type) const { return history.has(p_type); };
void clear_history();
HistoryList();
};
class CreateDialog : public ConfirmationDialog {
GDCLASS(CreateDialog, ConfirmationDialog);
LineEdit *search_box;
Tree *search_options;
Button *favorite;
Tree *result_tree;
EditorHelpBit *help_bit;
FavoriteList *favorite_list;
HistoryList *history_list;
String base_type;
String icon_fallback;
String preferred_search_result_type;
Button *favorite;
Vector<String> favorite_list;
Tree *favorites;
ItemList *recent;
EditorHelpBit *help_bit;
Vector<CreateDialogCandidate> candidates;
Set<StringName> blacklisted_types;
HashMap<String, TreeItem *> search_options_types;
HashMap<String, TreeItem *> result_tree_types;
HashMap<String, String> custom_type_parents;
HashMap<String, int> custom_type_indices;
List<StringName> type_list;
Set<StringName> type_blacklist;
void _update_search();
Vector<CreateDialogCandidate> _compute_candidates();
bool _should_hide_type(const String &p_type) const;
void _add_type(const String &p_current, bool p_cpp_type);
void _configure_search_option_item(TreeItem *r_item, const String &p_type, const bool p_cpp_type);
String _top_result(const Vector<String> p_candidates, const String &p_search_text) const;
float _score_type(const String &p_type, const String &p_search) const;
bool _is_type_preferred(const String &p_type) const;
void _fill_type_list();
void _cleanup();
void _update_result_tree();
void _add_type(const String &p_type, bool p_cpp_type);
TreeItem *_create_type(const String &p_type, TreeItem *p_parent_type, const bool p_cpp_type);
void _select_type(const String &p_type);
void _sbox_input(const Ref<InputEvent> &p_ie);
String _top_result(const Vector<CreateDialogCandidate> p_candidates, const String &p_query) const;
void _search_box_input(const Ref<InputEvent> &p_ie);
void _text_changed(const String &p_newtext);
void select_type(const String &p_type);
void _item_selected();
void _hide_requested();
void _confirmed();
virtual void cancel_pressed() override;
void _favorite_toggled();
void _hide_requested();
void _confirmed();
void _history_selected(int p_idx);
void _favorite_selected();
void _history_activated(int p_idx);
void _favorite_selected();
void _favorite_activated();
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
virtual void cancel_pressed() override;
bool _is_class_disabled_by_feature_profile(const StringName &p_class) const;
void _load_favorites_and_history();
void _cleanup();
protected:
void _notification(int p_what);
static void _bind_methods();
void _save_and_update_favorite_list();
public:
void popup_create(bool p_dont_clear, bool p_replace_mode = false, const String &p_select_type = "Node");
Variant instance_selected();
String get_selected_type();
@ -109,9 +160,6 @@ public:
String get_base_type() const { return base_type; }
void set_preferred_search_result_type(const String &p_preferred_type) { preferred_search_result_type = p_preferred_type; }
String get_preferred_search_result_type() { return preferred_search_result_type; }
void popup_create(bool p_dont_clear, bool p_replace_mode = false, const String &p_select_type = "Node");
CreateDialog();
};

View file

@ -844,6 +844,10 @@ Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() {
return current;
}
bool EditorFeatureProfileManager::is_class_disabled(const StringName &p_class) const {
return !current.is_null() && current->is_class_disabled(p_class);
}
EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = nullptr;
void EditorFeatureProfileManager::_bind_methods() {

View file

@ -176,6 +176,7 @@ protected:
public:
Ref<EditorFeatureProfile> get_current_profile();
void notify_changed();
bool is_class_disabled(const StringName &p_class) const;
static EditorFeatureProfileManager *get_singleton() { return singleton; }
EditorFeatureProfileManager();

View file

@ -0,0 +1,172 @@
/*************************************************************************/
/* test_create_dialog_candidate.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#ifndef TEST_CREATE_DIALOG_CANDIDATE_H
#define TEST_CREATE_DIALOG_CANDIDATE_H
#include "thirdparty/doctest/doctest.h"
#include "editor/create_dialog.h"
TEST_SUITE("[Candidate] isValid") {
TEST_CASE("Substring match") {
SUBCASE("Candidate with matching query.") {
CreateDialogCandidate candidate("Node", false, false, false);
CHECK(candidate.is_valid("node"));
}
SUBCASE("Should be valid with query should be case insensitive.") {
CreateDialogCandidate candidate("Node", false, false, false);
CHECK(candidate.is_valid("nODe"));
}
SUBCASE("Should be valid with query as a substring starting at the beginning.") {
CreateDialogCandidate candidate("Node", false, false, false);
CHECK(candidate.is_valid("nod"));
}
SUBCASE("Should be valid with query as a substring in the middle.") {
CreateDialogCandidate candidate("GraphNode", false, false, false);
CHECK(candidate.is_valid("nod"));
}
SUBCASE("Should be invalid with subequence query.") {
CreateDialogCandidate candidate("GraphNode", false, false, false);
CHECK(!candidate.is_valid("grph"));
}
SUBCASE("Should be invalid with excessive query.") {
CreateDialogCandidate candidate("GraphNode", false, false, false);
CHECK(!candidate.is_valid("grph"));
}
}
TEST_CASE("Word boundary") {
SUBCASE("Should be valid with subsequence match based on word boundary characters.") {
CreateDialogCandidate candidate("AnimationPlayer", false, false, false);
CHECK(candidate.is_valid("ap"));
}
SUBCASE("Should be valid with digit as word boundary character.") {
CreateDialogCandidate candidate("Node3D", false, false, false);
CHECK(candidate.is_valid("N3"));
}
SUBCASE("Should be invalid for query with non-word boundary matches.") {
CreateDialogCandidate candidate("AnimationPlayer", false, false, false);
CHECK(!candidate.is_valid("apl"));
}
SUBCASE("Should be valid with subsequence match on word boundary characters.") {
CreateDialogCandidate candidate("CharacterBody3D", false, false, false);
CHECK(candidate.is_valid("cbd"));
}
SUBCASE("Should be invalid without word boundary overlap.") {
CreateDialogCandidate candidate("AnimationPlayer", false, false, false);
CHECK(!candidate.is_valid("ai"));
}
}
}
TEST_SUITE("[Candidate] Score") {
TEST_CASE("Regular query") {
SUBCASE("Candidate with matching query should have maximum score.") {
CreateDialogCandidate candidate("Node", false, false, false);
CHECK(candidate.compute_score("node") == 1.0);
}
SUBCASE("Candidate which was a query match earlier in the string should score higher.") {
CreateDialogCandidate candidate("NodeGraph", false, false, false);
CreateDialogCandidate candidate2("GraphNode", false, false, false);
String query = "node";
CHECK(candidate.compute_score(query) > candidate2.compute_score(query));
}
SUBCASE("Candidate with shorter name should score higher.") {
CreateDialogCandidate candidate("Path2D", false, false, false);
CreateDialogCandidate candidate2("PathFollow2D", false, false, false);
String query = "path";
CHECK(candidate.compute_score(query) > candidate2.compute_score(query));
}
}
TEST_CASE("Word boundary query") {
SUBCASE("Substring match in the middle should score lower than word boundary match.") {
CreateDialogCandidate candidate("CheckBox", false, false, false);
CreateDialogCandidate candidate2("StaticBody", false, false, false);
String query = "cb";
CHECK(candidate.compute_score(query) > candidate2.compute_score(query));
}
}
TEST_CASE("Secondary features") {
SUBCASE("Candidate with preferred type, should score higher.") {
CreateDialogCandidate candidate("Node3D", true, false, false);
CreateDialogCandidate candidate2("Node2D", false, false, false);
String query = "node";
CHECK(candidate.compute_score(query) > candidate2.compute_score(query));
}
SUBCASE("Candidate which is a favorite should score higher.") {
CreateDialogCandidate candidate("Node3D", false, true, false);
CreateDialogCandidate candidate2("Node2D", false, false, false);
String query = "node";
CHECK(candidate.compute_score(query) > candidate2.compute_score(query));
}
SUBCASE("Candidate which was recently created should score higher.") {
CreateDialogCandidate candidate("Node3D", false, false, true);
CreateDialogCandidate candidate2("Node2D", false, false, false);
String query = "node";
CHECK(candidate.compute_score(query) > candidate2.compute_score(query));
}
}
}
#endif // TEST_CREATE_DIALOG_CANDIDATE_H

View file

@ -41,6 +41,7 @@
#include "test_color.h"
#include "test_command_queue.h"
#include "test_config_file.h"
#include "test_create_dialog_candidate.h"
#include "test_crypto.h"
#include "test_curve.h"
#include "test_dictionary.h"