Merge 7ba036bdcf
into 38c698c485
This commit is contained in:
commit
5946b93e4a
|
@ -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());
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
172
tests/test_create_dialog_candidate.h
Normal file
172
tests/test_create_dialog_candidate.h
Normal 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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue