mirror of
https://github.com/godotengine/godot.git
synced 2024-11-21 03:18:37 +08:00
Add fuzzy string matching to quick open search
Co-authored-by: sam <samsface@gmail.com>
This commit is contained in:
parent
a3080477ac
commit
3ac043c508
349
core/string/fuzzy_search.cpp
Normal file
349
core/string/fuzzy_search.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
/**************************************************************************/
|
||||
/* fuzzy_search.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 "fuzzy_search.h"
|
||||
|
||||
constexpr float cull_factor = 0.1f;
|
||||
constexpr float cull_cutoff = 30.0f;
|
||||
const String boundary_chars = "/\\-_.";
|
||||
|
||||
static bool _is_valid_interval(const Vector2i &p_interval) {
|
||||
// Empty intervals are represented as (-1, -1).
|
||||
return p_interval.x >= 0 && p_interval.y >= p_interval.x;
|
||||
}
|
||||
|
||||
static Vector2i _extend_interval(const Vector2i &p_a, const Vector2i &p_b) {
|
||||
if (!_is_valid_interval(p_a)) {
|
||||
return p_b;
|
||||
}
|
||||
if (!_is_valid_interval(p_b)) {
|
||||
return p_a;
|
||||
}
|
||||
return Vector2i(MIN(p_a.x, p_b.x), MAX(p_a.y, p_b.y));
|
||||
}
|
||||
|
||||
static bool _is_word_boundary(const String &p_str, int p_index) {
|
||||
if (p_index == -1 || p_index == p_str.size()) {
|
||||
return true;
|
||||
}
|
||||
return boundary_chars.find_char(p_str[p_index]) != -1;
|
||||
}
|
||||
|
||||
bool FuzzySearchToken::try_exact_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset) const {
|
||||
p_match.token_idx = idx;
|
||||
p_match.token_length = string.length();
|
||||
int match_idx = p_target.find(string, p_offset);
|
||||
if (match_idx == -1) {
|
||||
return false;
|
||||
}
|
||||
p_match.add_substring(match_idx, string.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FuzzySearchToken::try_fuzzy_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset, int p_miss_budget) const {
|
||||
p_match.token_idx = idx;
|
||||
p_match.token_length = string.length();
|
||||
int run_start = -1;
|
||||
int run_len = 0;
|
||||
|
||||
// Search for the subsequence p_token in p_target starting from p_offset, recording each substring for
|
||||
// later scoring and display.
|
||||
for (int i = 0; i < string.length(); i++) {
|
||||
int new_offset = p_target.find_char(string[i], p_offset);
|
||||
if (new_offset < 0) {
|
||||
p_miss_budget--;
|
||||
if (p_miss_budget < 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (run_start == -1 || p_offset != new_offset) {
|
||||
if (run_start != -1) {
|
||||
p_match.add_substring(run_start, run_len);
|
||||
}
|
||||
run_start = new_offset;
|
||||
run_len = 1;
|
||||
} else {
|
||||
run_len += 1;
|
||||
}
|
||||
p_offset = new_offset + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (run_start != -1) {
|
||||
p_match.add_substring(run_start, run_len);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FuzzyTokenMatch::add_substring(int p_substring_start, int p_substring_length) {
|
||||
substrings.append(Vector2i(p_substring_start, p_substring_length));
|
||||
matched_length += p_substring_length;
|
||||
Vector2i substring_interval = { p_substring_start, p_substring_start + p_substring_length - 1 };
|
||||
interval = _extend_interval(interval, substring_interval);
|
||||
}
|
||||
|
||||
bool FuzzyTokenMatch::intersects(const Vector2i &p_other_interval) const {
|
||||
if (!_is_valid_interval(interval) || !_is_valid_interval(p_other_interval)) {
|
||||
return false;
|
||||
}
|
||||
return interval.y >= p_other_interval.x && interval.x <= p_other_interval.y;
|
||||
}
|
||||
|
||||
bool FuzzySearchResult::can_add_token_match(const FuzzyTokenMatch &p_match) const {
|
||||
if (p_match.get_miss_count() > miss_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_match.intersects(match_interval)) {
|
||||
if (token_matches.size() == 1) {
|
||||
return false;
|
||||
}
|
||||
for (const FuzzyTokenMatch &existing_match : token_matches) {
|
||||
if (existing_match.intersects(p_match.interval)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FuzzyTokenMatch::is_case_insensitive(const String &p_original, const String &p_adjusted) const {
|
||||
for (const Vector2i &substr : substrings) {
|
||||
const int end = substr.x + substr.y;
|
||||
for (int i = substr.x; i < end; i++) {
|
||||
if (p_original[i] != p_adjusted[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FuzzySearchResult::score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const {
|
||||
// This can always be tweaked more. The intuition is that exact matches should almost always
|
||||
// be prioritized over broken up matches, and other criteria more or less act as tie breakers.
|
||||
|
||||
p_match.score = -20 * p_match.get_miss_count() - (p_case_insensitive ? 3 : 0);
|
||||
|
||||
for (const Vector2i &substring : p_match.substrings) {
|
||||
// Score longer substrings higher than short substrings.
|
||||
int substring_score = substring.y * substring.y;
|
||||
// Score matches deeper in path higher than shallower matches
|
||||
if (substring.x > dir_index) {
|
||||
substring_score *= 2;
|
||||
}
|
||||
// Score matches on a word boundary higher than matches within a word
|
||||
if (_is_word_boundary(target, substring.x - 1) || _is_word_boundary(target, substring.x + substring.y)) {
|
||||
substring_score += 4;
|
||||
}
|
||||
// Score exact query matches higher than non-compact subsequence matches
|
||||
if (substring.y == p_match.token_length) {
|
||||
substring_score += 100;
|
||||
}
|
||||
p_match.score += substring_score;
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzySearchResult::maybe_apply_score_bonus() {
|
||||
// This adds a small bonus to results which match tokens in the same order they appear in the query.
|
||||
int *token_range_starts = (int *)alloca(sizeof(int) * token_matches.size());
|
||||
|
||||
for (const FuzzyTokenMatch &match : token_matches) {
|
||||
token_range_starts[match.token_idx] = match.interval.x;
|
||||
}
|
||||
|
||||
int last = token_range_starts[0];
|
||||
for (int i = 1; i < token_matches.size(); i++) {
|
||||
if (last > token_range_starts[i]) {
|
||||
return;
|
||||
}
|
||||
last = token_range_starts[i];
|
||||
}
|
||||
|
||||
score += 1;
|
||||
}
|
||||
|
||||
void FuzzySearchResult::add_token_match(const FuzzyTokenMatch &p_match) {
|
||||
score += p_match.score;
|
||||
match_interval = _extend_interval(match_interval, p_match.interval);
|
||||
miss_budget -= p_match.get_miss_count();
|
||||
token_matches.append(p_match);
|
||||
}
|
||||
|
||||
void remove_low_scores(Vector<FuzzySearchResult> &p_results, float p_cull_score) {
|
||||
// Removes all results with score < p_cull_score in-place.
|
||||
int i = 0;
|
||||
int j = p_results.size() - 1;
|
||||
FuzzySearchResult *results = p_results.ptrw();
|
||||
|
||||
while (true) {
|
||||
// Advances i to an element to remove and j to an element to keep.
|
||||
while (j >= i && results[j].score < p_cull_score) {
|
||||
j--;
|
||||
}
|
||||
while (i < j && results[i].score >= p_cull_score) {
|
||||
i++;
|
||||
}
|
||||
if (i >= j) {
|
||||
break;
|
||||
}
|
||||
results[i++] = results[j--];
|
||||
}
|
||||
|
||||
p_results.resize(j + 1);
|
||||
}
|
||||
|
||||
void FuzzySearch::sort_and_filter(Vector<FuzzySearchResult> &p_results) const {
|
||||
if (p_results.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float avg_score = 0;
|
||||
float max_score = 0;
|
||||
|
||||
for (const FuzzySearchResult &result : p_results) {
|
||||
avg_score += result.score;
|
||||
max_score = MAX(max_score, result.score);
|
||||
}
|
||||
|
||||
// TODO: Tune scoring and culling here to display fewer subsequence soup matches when good matches
|
||||
// are available.
|
||||
avg_score /= p_results.size();
|
||||
float cull_score = MIN(cull_cutoff, Math::lerp(avg_score, max_score, cull_factor));
|
||||
remove_low_scores(p_results, cull_score);
|
||||
|
||||
struct FuzzySearchResultComparator {
|
||||
bool operator()(const FuzzySearchResult &p_lhs, const FuzzySearchResult &p_rhs) const {
|
||||
// Sort on (score, length, alphanumeric) to ensure consistent ordering.
|
||||
if (p_lhs.score == p_rhs.score) {
|
||||
if (p_lhs.target.length() == p_rhs.target.length()) {
|
||||
return p_lhs.target < p_rhs.target;
|
||||
}
|
||||
return p_lhs.target.length() < p_rhs.target.length();
|
||||
}
|
||||
return p_lhs.score > p_rhs.score;
|
||||
}
|
||||
};
|
||||
|
||||
SortArray<FuzzySearchResult, FuzzySearchResultComparator> sorter;
|
||||
|
||||
if (p_results.size() > max_results) {
|
||||
sorter.partial_sort(0, p_results.size(), max_results, p_results.ptrw());
|
||||
p_results.resize(max_results);
|
||||
} else {
|
||||
sorter.sort(p_results.ptrw(), p_results.size());
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzySearch::set_query(const String &p_query) {
|
||||
tokens.clear();
|
||||
for (const String &string : p_query.split(" ", false)) {
|
||||
tokens.append({ static_cast<int>(tokens.size()), string });
|
||||
}
|
||||
|
||||
case_sensitive = !p_query.is_lowercase();
|
||||
|
||||
struct TokenComparator {
|
||||
bool operator()(const FuzzySearchToken &A, const FuzzySearchToken &B) const {
|
||||
if (A.string.length() == B.string.length()) {
|
||||
return A.idx < B.idx;
|
||||
}
|
||||
return A.string.length() > B.string.length();
|
||||
}
|
||||
};
|
||||
|
||||
// Prioritize matching longer tokens before shorter ones since match overlaps are not accepted.
|
||||
tokens.sort_custom<TokenComparator>();
|
||||
}
|
||||
|
||||
bool FuzzySearch::search(const String &p_target, FuzzySearchResult &p_result) const {
|
||||
p_result.target = p_target;
|
||||
p_result.dir_index = p_target.rfind_char('/');
|
||||
p_result.miss_budget = max_misses;
|
||||
|
||||
String adjusted_target = case_sensitive ? p_target : p_target.to_lower();
|
||||
|
||||
// For each token, eagerly generate subsequences starting from index 0 and keep the best scoring one
|
||||
// which does not conflict with prior token matches. This is not ensured to find the highest scoring
|
||||
// combination of matches, or necessarily the highest scoring single subsequence, as it only considers
|
||||
// eager subsequences for a given index, and likewise eagerly finds matches for each token in sequence.
|
||||
for (const FuzzySearchToken &token : tokens) {
|
||||
FuzzyTokenMatch best_match;
|
||||
int offset = start_offset;
|
||||
|
||||
while (true) {
|
||||
FuzzyTokenMatch match;
|
||||
if (allow_subsequences) {
|
||||
if (!token.try_fuzzy_match(match, adjusted_target, offset, p_result.miss_budget)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!token.try_exact_match(match, adjusted_target, offset)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p_result.can_add_token_match(match)) {
|
||||
p_result.score_token_match(match, match.is_case_insensitive(p_target, adjusted_target));
|
||||
if (best_match.token_idx == -1 || best_match.score < match.score) {
|
||||
best_match = match;
|
||||
}
|
||||
}
|
||||
if (_is_valid_interval(match.interval)) {
|
||||
offset = match.interval.x + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_match.token_idx == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p_result.add_token_match(best_match);
|
||||
}
|
||||
|
||||
p_result.maybe_apply_score_bonus();
|
||||
return true;
|
||||
}
|
||||
|
||||
void FuzzySearch::search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const {
|
||||
p_results.clear();
|
||||
|
||||
for (const String &target : p_targets) {
|
||||
FuzzySearchResult result;
|
||||
if (search(target, result)) {
|
||||
p_results.append(result);
|
||||
}
|
||||
}
|
||||
|
||||
sort_and_filter(p_results);
|
||||
}
|
101
core/string/fuzzy_search.h
Normal file
101
core/string/fuzzy_search.h
Normal file
@ -0,0 +1,101 @@
|
||||
/**************************************************************************/
|
||||
/* fuzzy_search.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef FUZZY_SEARCH_H
|
||||
#define FUZZY_SEARCH_H
|
||||
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class FuzzyTokenMatch;
|
||||
|
||||
struct FuzzySearchToken {
|
||||
int idx = -1;
|
||||
String string;
|
||||
|
||||
bool try_exact_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset) const;
|
||||
bool try_fuzzy_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset, int p_miss_budget) const;
|
||||
};
|
||||
|
||||
class FuzzyTokenMatch {
|
||||
friend struct FuzzySearchToken;
|
||||
friend class FuzzySearchResult;
|
||||
friend class FuzzySearch;
|
||||
|
||||
int matched_length = 0;
|
||||
int token_length = 0;
|
||||
int token_idx = -1;
|
||||
Vector2i interval = Vector2i(-1, -1); // x and y are both inclusive indices.
|
||||
|
||||
void add_substring(int p_substring_start, int p_substring_length);
|
||||
bool intersects(const Vector2i &p_other_interval) const;
|
||||
bool is_case_insensitive(const String &p_original, const String &p_adjusted) const;
|
||||
int get_miss_count() const { return token_length - matched_length; }
|
||||
|
||||
public:
|
||||
int score = 0;
|
||||
Vector<Vector2i> substrings; // x is start index, y is length.
|
||||
};
|
||||
|
||||
class FuzzySearchResult {
|
||||
friend class FuzzySearch;
|
||||
|
||||
int miss_budget = 0;
|
||||
Vector2i match_interval = Vector2i(-1, -1);
|
||||
|
||||
bool can_add_token_match(const FuzzyTokenMatch &p_match) const;
|
||||
void score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const;
|
||||
void add_token_match(const FuzzyTokenMatch &p_match);
|
||||
void maybe_apply_score_bonus();
|
||||
|
||||
public:
|
||||
String target;
|
||||
int score = 0;
|
||||
int dir_index = -1;
|
||||
Vector<FuzzyTokenMatch> token_matches;
|
||||
};
|
||||
|
||||
class FuzzySearch {
|
||||
Vector<FuzzySearchToken> tokens;
|
||||
|
||||
void sort_and_filter(Vector<FuzzySearchResult> &p_results) const;
|
||||
|
||||
public:
|
||||
int start_offset = 0;
|
||||
bool case_sensitive = false;
|
||||
int max_results = 100;
|
||||
int max_misses = 2;
|
||||
bool allow_subsequences = true;
|
||||
|
||||
void set_query(const String &p_query);
|
||||
bool search(const String &p_target, FuzzySearchResult &p_result) const;
|
||||
void search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const;
|
||||
};
|
||||
|
||||
#endif // FUZZY_SEARCH_H
|
@ -3372,7 +3372,7 @@ int String::find(const char *p_str, int p_from) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int String::find_char(const char32_t &p_char, int p_from) const {
|
||||
int String::find_char(char32_t p_char, int p_from) const {
|
||||
return _cowdata.find(p_char, p_from);
|
||||
}
|
||||
|
||||
@ -3609,6 +3609,10 @@ int String::rfind(const char *p_str, int p_from) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int String::rfind_char(char32_t p_char, int p_from) const {
|
||||
return _cowdata.rfind(p_char, p_from);
|
||||
}
|
||||
|
||||
int String::rfindn(const String &p_str, int p_from) const {
|
||||
// establish a limit
|
||||
int limit = length() - p_str.length();
|
||||
@ -3822,6 +3826,15 @@ bool String::is_quoted() const {
|
||||
return is_enclosed_in("\"") || is_enclosed_in("'");
|
||||
}
|
||||
|
||||
bool String::is_lowercase() const {
|
||||
for (const char32_t *str = &operator[](0); *str; str++) {
|
||||
if (is_unicode_upper_case(*str)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const {
|
||||
if (p_string.is_empty()) {
|
||||
return 0;
|
||||
|
@ -287,11 +287,12 @@ public:
|
||||
String substr(int p_from, int p_chars = -1) const;
|
||||
int find(const String &p_str, int p_from = 0) const; ///< return <0 if failed
|
||||
int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed
|
||||
int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed
|
||||
int find_char(char32_t p_char, int p_from = 0) const; ///< return <0 if failed
|
||||
int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive
|
||||
int findn(const char *p_str, int p_from = 0) const; ///< return <0 if failed
|
||||
int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed
|
||||
int rfind(const char *p_str, int p_from = -1) const; ///< return <0 if failed
|
||||
int rfind_char(char32_t p_char, int p_from = -1) const; ///< return <0 if failed
|
||||
int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive
|
||||
int rfindn(const char *p_str, int p_from = -1) const; ///< return <0 if failed
|
||||
int findmk(const Vector<String> &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed
|
||||
@ -305,6 +306,7 @@ public:
|
||||
bool is_subsequence_of(const String &p_string) const;
|
||||
bool is_subsequence_ofn(const String &p_string) const;
|
||||
bool is_quoted() const;
|
||||
bool is_lowercase() const;
|
||||
Vector<String> bigrams() const;
|
||||
float similarity(const String &p_string) const;
|
||||
String format(const Variant &values, const String &placeholder = "{_}") const;
|
||||
|
@ -717,9 +717,21 @@
|
||||
<member name="filesystem/quick_open_dialog/default_display_mode" type="int" setter="" getter="">
|
||||
If set to [code]Adaptive[/code], the dialog opens in list view or grid view depending on the requested type. If set to [code]Last Used[/code], the display mode will always open the way you last used it.
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/enable_fuzzy_matching" type="bool" setter="" getter="">
|
||||
If [code]true[/code], fuzzy matching of search tokens is allowed.
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/include_addons" type="bool" setter="" getter="">
|
||||
If [code]true[/code], results will include files located in the [code]addons[/code] folder.
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/max_fuzzy_misses" type="int" setter="" getter="">
|
||||
The number of allowed missed query characters in a match, if fuzzy matching is enabled. For example, with the default value of 2, [code]foobar[/code] would match [code]foobur[/code] and [code]foob[/code] but not [code]foo[/code].
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/max_results" type="int" setter="" getter="">
|
||||
Maximum number of matches to show in dialog.
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/show_search_highlight" type="bool" setter="" getter="">
|
||||
If [code]true[/code], results will be highlighted with their search matches.
|
||||
</member>
|
||||
<member name="filesystem/tools/oidn/oidn_denoise_path" type="String" setter="" getter="">
|
||||
The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url].
|
||||
To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser].
|
||||
|
@ -602,6 +602,10 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
|
||||
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16")
|
||||
|
||||
// Quick Open dialog
|
||||
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/quick_open_dialog/max_results", 100, "0,10000,1", PROPERTY_USAGE_DEFAULT)
|
||||
_initial_set("filesystem/quick_open_dialog/show_search_highlight", true);
|
||||
_initial_set("filesystem/quick_open_dialog/enable_fuzzy_matching", true);
|
||||
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/quick_open_dialog/max_fuzzy_misses", 2, "0,10,1", PROPERTY_USAGE_DEFAULT)
|
||||
_initial_set("filesystem/quick_open_dialog/include_addons", false);
|
||||
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "filesystem/quick_open_dialog/default_display_mode", 0, "Adaptive,Last Used")
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include "editor_quick_open_dialog.h"
|
||||
|
||||
#include "core/string/fuzzy_search.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_resource_preview.h"
|
||||
@ -45,6 +46,55 @@
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
void HighlightedLabel::draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing) {
|
||||
for (int i = get_lines_skipped(); i < p_line_limit; i++) {
|
||||
RID line = get_line_rid(i);
|
||||
Vector<Vector2> ranges = TS->shaped_text_get_selection(line, p_substr.x, p_substr.x + p_substr.y);
|
||||
Rect2 line_rect = get_line_rect(i);
|
||||
for (const Vector2 &range : ranges) {
|
||||
Rect2 rect = Rect2(Point2(range.x, 0) + line_rect.position, Size2(range.y - range.x, line_rect.size.y));
|
||||
rect.position = p_offset + line_rect.position;
|
||||
rect.position.x += range.x;
|
||||
rect.size = Size2(range.y - range.x, line_rect.size.y);
|
||||
rect.size.x = MIN(rect.size.x, line_rect.size.x - range.x);
|
||||
if (rect.size.x > 0) {
|
||||
draw_rect(rect, Color(1, 1, 1, 0.07), true);
|
||||
draw_rect(rect, Color(0.5, 0.7, 1.0, 0.4), false, 1);
|
||||
}
|
||||
}
|
||||
p_offset.y += line_spacing + TS->shaped_text_get_ascent(line) + TS->shaped_text_get_descent(line);
|
||||
}
|
||||
}
|
||||
|
||||
void HighlightedLabel::add_highlight(const Vector2i &p_interval) {
|
||||
if (p_interval.y > 0) {
|
||||
highlights.append(p_interval);
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void HighlightedLabel::reset_highlights() {
|
||||
highlights.clear();
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void HighlightedLabel::_notification(int p_notification) {
|
||||
if (p_notification == NOTIFICATION_DRAW) {
|
||||
if (highlights.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 offset;
|
||||
int line_limit;
|
||||
int line_spacing;
|
||||
get_layout_data(offset, line_limit, line_spacing);
|
||||
|
||||
for (const Vector2i &substr : highlights) {
|
||||
draw_substr_rects(substr, offset, line_limit, line_spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorQuickOpenDialog::EditorQuickOpenDialog() {
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
vbc->add_theme_constant_override("separation", 0);
|
||||
@ -100,7 +150,7 @@ void EditorQuickOpenDialog::popup_dialog(const Vector<StringName> &p_base_types,
|
||||
get_ok_button()->set_disabled(container->has_nothing_selected());
|
||||
|
||||
set_title(get_dialog_title(p_base_types));
|
||||
popup_centered_clamped(Size2(710, 650) * EDSCALE, 0.8f);
|
||||
popup_centered_clamped(Size2(780, 650) * EDSCALE, 0.8f);
|
||||
search_box->grab_focus();
|
||||
}
|
||||
|
||||
@ -119,13 +169,18 @@ void EditorQuickOpenDialog::cancel_pressed() {
|
||||
}
|
||||
|
||||
void EditorQuickOpenDialog::_search_box_text_changed(const String &p_query) {
|
||||
container->update_results(p_query.to_lower());
|
||||
|
||||
container->set_query_and_update(p_query);
|
||||
get_ok_button()->set_disabled(container->has_nothing_selected());
|
||||
}
|
||||
|
||||
//------------------------- Result Container
|
||||
|
||||
void style_button(Button *p_button) {
|
||||
p_button->set_flat(true);
|
||||
p_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
p_button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND);
|
||||
}
|
||||
|
||||
QuickOpenResultContainer::QuickOpenResultContainer() {
|
||||
set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
@ -175,91 +230,107 @@ QuickOpenResultContainer::QuickOpenResultContainer() {
|
||||
}
|
||||
|
||||
{
|
||||
// Bottom bar
|
||||
HBoxContainer *bottom_bar = memnew(HBoxContainer);
|
||||
add_child(bottom_bar);
|
||||
|
||||
// Selected filepath
|
||||
file_details_path = memnew(Label);
|
||||
file_details_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
file_details_path->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
|
||||
file_details_path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
bottom_bar->add_child(file_details_path);
|
||||
|
||||
{
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
hbc->add_theme_constant_override("separation", 3);
|
||||
bottom_bar->add_child(hbc);
|
||||
|
||||
include_addons_toggle = memnew(CheckButton);
|
||||
include_addons_toggle->set_flat(true);
|
||||
include_addons_toggle->set_focus_mode(Control::FOCUS_NONE);
|
||||
include_addons_toggle->set_default_cursor_shape(CURSOR_POINTING_HAND);
|
||||
include_addons_toggle->set_tooltip_text(TTR("Include files from addons"));
|
||||
include_addons_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_include_addons));
|
||||
hbc->add_child(include_addons_toggle);
|
||||
|
||||
VSeparator *vsep = memnew(VSeparator);
|
||||
vsep->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
vsep->set_custom_minimum_size(Size2i(0, 14 * EDSCALE));
|
||||
hbc->add_child(vsep);
|
||||
|
||||
display_mode_toggle = memnew(Button);
|
||||
display_mode_toggle->set_flat(true);
|
||||
display_mode_toggle->set_focus_mode(Control::FOCUS_NONE);
|
||||
display_mode_toggle->set_default_cursor_shape(CURSOR_POINTING_HAND);
|
||||
display_mode_toggle->connect(SceneStringName(pressed), callable_mp(this, &QuickOpenResultContainer::_toggle_display_mode));
|
||||
hbc->add_child(display_mode_toggle);
|
||||
}
|
||||
add_child(file_details_path);
|
||||
}
|
||||
|
||||
// Creating and deleting nodes while searching is slow, so we allocate
|
||||
// a bunch of result nodes and fill in the content based on result ranking.
|
||||
result_items.resize(TOTAL_ALLOCATED_RESULT_ITEMS);
|
||||
for (int i = 0; i < TOTAL_ALLOCATED_RESULT_ITEMS; i++) {
|
||||
QuickOpenResultItem *item = memnew(QuickOpenResultItem);
|
||||
item->connect(SceneStringName(gui_input), callable_mp(this, &QuickOpenResultContainer::_item_input).bind(i));
|
||||
result_items.write[i] = item;
|
||||
{
|
||||
// Bottom bar
|
||||
HBoxContainer *bottom_bar = memnew(HBoxContainer);
|
||||
bottom_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
bottom_bar->set_alignment(ALIGNMENT_END);
|
||||
bottom_bar->add_theme_constant_override("separation", 3);
|
||||
add_child(bottom_bar);
|
||||
|
||||
fuzzy_search_toggle = memnew(CheckButton);
|
||||
style_button(fuzzy_search_toggle);
|
||||
fuzzy_search_toggle->set_text(TTR("Fuzzy Search"));
|
||||
fuzzy_search_toggle->set_tooltip_text(TTR("Enable fuzzy matching"));
|
||||
fuzzy_search_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_fuzzy_search));
|
||||
bottom_bar->add_child(fuzzy_search_toggle);
|
||||
|
||||
include_addons_toggle = memnew(CheckButton);
|
||||
style_button(include_addons_toggle);
|
||||
include_addons_toggle->set_text(TTR("Addons"));
|
||||
include_addons_toggle->set_tooltip_text(TTR("Include files from addons"));
|
||||
include_addons_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_include_addons));
|
||||
bottom_bar->add_child(include_addons_toggle);
|
||||
|
||||
VSeparator *vsep = memnew(VSeparator);
|
||||
vsep->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
vsep->set_custom_minimum_size(Size2i(0, 14 * EDSCALE));
|
||||
bottom_bar->add_child(vsep);
|
||||
|
||||
display_mode_toggle = memnew(Button);
|
||||
style_button(display_mode_toggle);
|
||||
display_mode_toggle->connect(SceneStringName(pressed), callable_mp(this, &QuickOpenResultContainer::_toggle_display_mode));
|
||||
bottom_bar->add_child(display_mode_toggle);
|
||||
}
|
||||
}
|
||||
|
||||
QuickOpenResultContainer::~QuickOpenResultContainer() {
|
||||
if (never_opened) {
|
||||
for (QuickOpenResultItem *E : result_items) {
|
||||
memdelete(E);
|
||||
void QuickOpenResultContainer::_ensure_result_vector_capacity() {
|
||||
int target_size = EDITOR_GET("filesystem/quick_open_dialog/max_results");
|
||||
int initial_size = result_items.size();
|
||||
for (int i = target_size; i < initial_size; i++) {
|
||||
result_items[i]->queue_free();
|
||||
}
|
||||
result_items.resize(target_size);
|
||||
for (int i = initial_size; i < target_size; i++) {
|
||||
QuickOpenResultItem *item = memnew(QuickOpenResultItem);
|
||||
item->connect(SceneStringName(gui_input), callable_mp(this, &QuickOpenResultContainer::_item_input).bind(i));
|
||||
result_items.write[i] = item;
|
||||
if (!never_opened) {
|
||||
_layout_result_item(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
|
||||
_ensure_result_vector_capacity();
|
||||
base_types = p_base_types;
|
||||
never_opened = false;
|
||||
|
||||
const int display_mode_behavior = EDITOR_GET("filesystem/quick_open_dialog/default_display_mode");
|
||||
const bool adaptive_display_mode = (display_mode_behavior == 0);
|
||||
|
||||
if (adaptive_display_mode) {
|
||||
_set_display_mode(get_adaptive_display_mode(p_base_types));
|
||||
} else if (never_opened) {
|
||||
int last = EditorSettings::get_singleton()->get_project_metadata("quick_open_dialog", "last_mode", (int)QuickOpenDisplayMode::LIST);
|
||||
_set_display_mode((QuickOpenDisplayMode)last);
|
||||
}
|
||||
|
||||
const bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
|
||||
const bool include_addons = EDITOR_GET("filesystem/quick_open_dialog/include_addons");
|
||||
fuzzy_search_toggle->set_pressed_no_signal(fuzzy_matching);
|
||||
include_addons_toggle->set_pressed_no_signal(include_addons);
|
||||
never_opened = false;
|
||||
|
||||
_create_initial_results(include_addons);
|
||||
const bool enable_highlights = EDITOR_GET("filesystem/quick_open_dialog/show_search_highlight");
|
||||
for (QuickOpenResultItem *E : result_items) {
|
||||
E->enable_highlights = enable_highlights;
|
||||
}
|
||||
|
||||
_create_initial_results();
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_create_initial_results(bool p_include_addons) {
|
||||
file_type_icons.insert("__default_icon", get_editor_theme_icon(SNAME("Object")));
|
||||
_find_candidates_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), p_include_addons);
|
||||
max_total_results = MIN(candidates.size(), TOTAL_ALLOCATED_RESULT_ITEMS);
|
||||
void QuickOpenResultContainer::_create_initial_results() {
|
||||
file_type_icons.clear();
|
||||
|
||||
update_results(query);
|
||||
file_type_icons.insert("__default_icon", get_editor_theme_icon(SNAME("Object")));
|
||||
filepaths.clear();
|
||||
filetypes.clear();
|
||||
_find_filepaths_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), include_addons_toggle->is_pressed());
|
||||
max_total_results = MIN(filepaths.size(), result_items.size());
|
||||
update_results();
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
|
||||
void QuickOpenResultContainer::_find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
|
||||
for (int i = 0; i < p_directory->get_subdir_count(); i++) {
|
||||
if (p_include_addons || p_directory->get_name() != "addons") {
|
||||
_find_candidates_in_folder(p_directory->get_subdir(i), p_include_addons);
|
||||
_find_filepaths_in_folder(p_directory->get_subdir(i), p_include_addons);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,146 +347,91 @@ void QuickOpenResultContainer::_find_candidates_in_folder(EditorFileSystemDirect
|
||||
bool is_valid = ClassDB::is_parent_class(engine_type, parent_type) || (!is_engine_type && EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type));
|
||||
|
||||
if (is_valid) {
|
||||
Candidate c;
|
||||
c.file_name = file_path.get_file();
|
||||
c.file_directory = file_path.get_base_dir();
|
||||
|
||||
EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(file_path);
|
||||
if (item.preview.is_valid()) {
|
||||
c.thumbnail = item.preview;
|
||||
} else if (file_type_icons.has(actual_type)) {
|
||||
c.thumbnail = *file_type_icons.lookup_ptr(actual_type);
|
||||
} else if (has_theme_icon(actual_type, EditorStringName(EditorIcons))) {
|
||||
c.thumbnail = get_editor_theme_icon(actual_type);
|
||||
file_type_icons.insert(actual_type, c.thumbnail);
|
||||
} else {
|
||||
c.thumbnail = *file_type_icons.lookup_ptr("__default_icon");
|
||||
}
|
||||
|
||||
candidates.push_back(c);
|
||||
|
||||
filepaths.append(file_path);
|
||||
filetypes.insert(file_path, actual_type);
|
||||
break; // Stop testing base types as soon as we get a match.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::update_results(const String &p_query) {
|
||||
void QuickOpenResultContainer::set_query_and_update(const String &p_query) {
|
||||
query = p_query;
|
||||
|
||||
int relevant_candidates = _sort_candidates(p_query);
|
||||
_update_result_items(MIN(relevant_candidates, max_total_results), 0);
|
||||
update_results();
|
||||
}
|
||||
|
||||
int QuickOpenResultContainer::_sort_candidates(const String &p_query) {
|
||||
if (p_query.is_empty()) {
|
||||
return 0;
|
||||
void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &candidate, const String &filepath) {
|
||||
StringName actual_type = *filetypes.lookup_ptr(filepath);
|
||||
candidate.file_path = filepath;
|
||||
candidate.result = nullptr;
|
||||
|
||||
EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(filepath);
|
||||
if (item.preview.is_valid()) {
|
||||
candidate.thumbnail = item.preview;
|
||||
} else if (file_type_icons.has(actual_type)) {
|
||||
candidate.thumbnail = *file_type_icons.lookup_ptr(actual_type);
|
||||
} else if (has_theme_icon(actual_type, EditorStringName(EditorIcons))) {
|
||||
candidate.thumbnail = get_editor_theme_icon(actual_type);
|
||||
file_type_icons.insert(actual_type, candidate.thumbnail);
|
||||
} else {
|
||||
candidate.thumbnail = *file_type_icons.lookup_ptr("__default_icon");
|
||||
}
|
||||
}
|
||||
|
||||
const PackedStringArray search_tokens = p_query.to_lower().replace("/", " ").split(" ", false);
|
||||
void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result) {
|
||||
_setup_candidate(p_candidate, p_result.target);
|
||||
p_candidate.result = &p_result;
|
||||
}
|
||||
|
||||
if (search_tokens.is_empty()) {
|
||||
return 0;
|
||||
void QuickOpenResultContainer::update_results() {
|
||||
showing_history = false;
|
||||
candidates.clear();
|
||||
if (query.is_empty()) {
|
||||
_use_default_candidates();
|
||||
} else {
|
||||
_score_and_sort_candidates();
|
||||
}
|
||||
_update_result_items(MIN(candidates.size(), max_total_results), 0);
|
||||
}
|
||||
|
||||
// First, we assign a score to each candidate.
|
||||
int num_relevant_candidates = 0;
|
||||
for (Candidate &c : candidates) {
|
||||
c.score = 0;
|
||||
int prev_token_match_pos = -1;
|
||||
|
||||
for (const String &token : search_tokens) {
|
||||
const int file_pos = c.file_name.findn(token);
|
||||
const int dir_pos = c.file_directory.findn(token);
|
||||
|
||||
const bool file_match = file_pos > -1;
|
||||
const bool dir_match = dir_pos > -1;
|
||||
if (!file_match && !dir_match) {
|
||||
c.score = -1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
float token_score = file_match ? 0.6f : 0.1999f;
|
||||
|
||||
// Add bias for shorter filenames/paths: they resemble the query more.
|
||||
const String &matched_string = file_match ? c.file_name : c.file_directory;
|
||||
int matched_string_token_pos = file_match ? file_pos : dir_pos;
|
||||
token_score += 0.1f * (1.0f - ((float)matched_string_token_pos / (float)matched_string.length()));
|
||||
|
||||
// Add bias if the match happened in the file name, not the extension.
|
||||
if (file_match) {
|
||||
int ext_pos = matched_string.rfind(".");
|
||||
if (ext_pos == -1 || ext_pos > matched_string_token_pos) {
|
||||
token_score += 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
// Add bias if token is in order.
|
||||
{
|
||||
int candidate_string_token_pos = file_match ? (c.file_directory.length() + file_pos) : dir_pos;
|
||||
|
||||
if (prev_token_match_pos != -1 && candidate_string_token_pos > prev_token_match_pos) {
|
||||
token_score += 0.2f;
|
||||
}
|
||||
|
||||
prev_token_match_pos = candidate_string_token_pos;
|
||||
}
|
||||
|
||||
c.score += token_score;
|
||||
void QuickOpenResultContainer::_use_default_candidates() {
|
||||
if (filepaths.size() <= SHOW_ALL_FILES_THRESHOLD) {
|
||||
candidates.resize(filepaths.size());
|
||||
QuickOpenResultCandidate *candidates_write = candidates.ptrw();
|
||||
for (const String &filepath : filepaths) {
|
||||
_setup_candidate(*candidates_write++, filepath);
|
||||
}
|
||||
|
||||
if (c.score > 0.0f) {
|
||||
num_relevant_candidates++;
|
||||
} else if (base_types.size() == 1) {
|
||||
Vector<QuickOpenResultCandidate> *history = selected_history.lookup_ptr(base_types[0]);
|
||||
if (history) {
|
||||
showing_history = true;
|
||||
candidates.append_array(*history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we will sort the candidates based on score, resolving ties by favoring:
|
||||
// 1. Shorter file length.
|
||||
// 2. Shorter directory length.
|
||||
// 3. Lower alphabetic order.
|
||||
struct CandidateComparator {
|
||||
_FORCE_INLINE_ bool operator()(const Candidate &p_a, const Candidate &p_b) const {
|
||||
if (!Math::is_equal_approx(p_a.score, p_b.score)) {
|
||||
return p_a.score > p_b.score;
|
||||
}
|
||||
void QuickOpenResultContainer::_update_fuzzy_search_results() {
|
||||
FuzzySearch fuzzy_search;
|
||||
fuzzy_search.start_offset = 6; // Don't match against "res://" at the start of each filepath.
|
||||
fuzzy_search.set_query(query);
|
||||
fuzzy_search.max_results = max_total_results;
|
||||
bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
|
||||
int max_misses = EDITOR_GET("filesystem/quick_open_dialog/max_fuzzy_misses");
|
||||
fuzzy_search.allow_subsequences = fuzzy_matching;
|
||||
fuzzy_search.max_misses = fuzzy_matching ? max_misses : 0;
|
||||
fuzzy_search.search_all(filepaths, search_results);
|
||||
}
|
||||
|
||||
if (p_a.file_name.length() != p_b.file_name.length()) {
|
||||
return p_a.file_name.length() < p_b.file_name.length();
|
||||
}
|
||||
|
||||
if (p_a.file_directory.length() != p_b.file_directory.length()) {
|
||||
return p_a.file_directory.length() < p_b.file_directory.length();
|
||||
}
|
||||
|
||||
return p_a.file_name < p_b.file_name;
|
||||
}
|
||||
};
|
||||
candidates.sort_custom<CandidateComparator>();
|
||||
|
||||
return num_relevant_candidates;
|
||||
void QuickOpenResultContainer::_score_and_sort_candidates() {
|
||||
_update_fuzzy_search_results();
|
||||
candidates.resize(search_results.size());
|
||||
QuickOpenResultCandidate *candidates_write = candidates.ptrw();
|
||||
for (const FuzzySearchResult &result : search_results) {
|
||||
_setup_candidate(*candidates_write++, result);
|
||||
}
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_count, int p_new_selection_index) {
|
||||
List<Candidate> *type_history = nullptr;
|
||||
|
||||
showing_history = false;
|
||||
|
||||
if (query.is_empty()) {
|
||||
if (candidates.size() <= SHOW_ALL_FILES_THRESHOLD) {
|
||||
p_new_visible_results_count = candidates.size();
|
||||
} else {
|
||||
p_new_visible_results_count = 0;
|
||||
|
||||
if (base_types.size() == 1) {
|
||||
type_history = selected_history.lookup_ptr(base_types[0]);
|
||||
if (type_history) {
|
||||
p_new_visible_results_count = type_history->size();
|
||||
showing_history = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only need to update items that were not hidden in previous update.
|
||||
int num_items_needing_updates = MAX(num_visible_results, p_new_visible_results_count);
|
||||
num_visible_results = p_new_visible_results_count;
|
||||
@ -424,13 +440,7 @@ void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_co
|
||||
QuickOpenResultItem *item = result_items[i];
|
||||
|
||||
if (i < num_visible_results) {
|
||||
if (type_history) {
|
||||
const Candidate &c = type_history->get(i);
|
||||
item->set_content(c.thumbnail, c.file_name, c.file_directory);
|
||||
} else {
|
||||
const Candidate &c = candidates[i];
|
||||
item->set_content(c.thumbnail, c.file_name, c.file_directory);
|
||||
}
|
||||
item->set_content(candidates[i]);
|
||||
} else {
|
||||
item->reset();
|
||||
}
|
||||
@ -443,7 +453,7 @@ void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_co
|
||||
no_results_container->set_visible(!any_results);
|
||||
|
||||
if (!any_results) {
|
||||
if (candidates.is_empty()) {
|
||||
if (filepaths.is_empty()) {
|
||||
no_results_label->set_text(TTR("No files found for this type"));
|
||||
} else if (query.is_empty()) {
|
||||
no_results_label->set_text(TTR("Start searching to find files..."));
|
||||
@ -471,10 +481,12 @@ void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_
|
||||
} break;
|
||||
case Key::LEFT:
|
||||
case Key::RIGHT: {
|
||||
// Both grid and the search box use left/right keys. By default, grid will take it.
|
||||
// It would be nice if we could check for ALT to give the event to the searchbox cursor.
|
||||
// However, if you press ALT, the searchbox also denies the input.
|
||||
move_selection = (content_display_mode == QuickOpenDisplayMode::GRID);
|
||||
if (content_display_mode == QuickOpenDisplayMode::GRID) {
|
||||
// Maybe strip off the shift modifier to allow non-selecting navigation by character?
|
||||
if (key_event->get_modifiers_mask() == 0) {
|
||||
move_selection = true;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break; // Let the event through so it will reach the search box.
|
||||
@ -562,11 +574,15 @@ void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_in
|
||||
}
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_toggle_fuzzy_search(bool p_pressed) {
|
||||
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/enable_fuzzy_matching", p_pressed);
|
||||
update_results();
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_toggle_include_addons(bool p_pressed) {
|
||||
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/include_addons", p_pressed);
|
||||
|
||||
cleanup();
|
||||
_create_initial_results(p_pressed);
|
||||
_create_initial_results();
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_toggle_display_mode() {
|
||||
@ -574,41 +590,41 @@ void QuickOpenResultContainer::_toggle_display_mode() {
|
||||
_set_display_mode(new_display_mode);
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
|
||||
content_display_mode = p_display_mode;
|
||||
CanvasItem *QuickOpenResultContainer::_get_result_root() {
|
||||
if (content_display_mode == QuickOpenDisplayMode::LIST) {
|
||||
return list;
|
||||
} else {
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
|
||||
const bool show_list = (content_display_mode == QuickOpenDisplayMode::LIST);
|
||||
if ((show_list && list->is_visible()) || (!show_list && grid->is_visible())) {
|
||||
void QuickOpenResultContainer::_layout_result_item(QuickOpenResultItem *item) {
|
||||
item->set_display_mode(content_display_mode);
|
||||
Node *parent = item->get_parent();
|
||||
if (parent) {
|
||||
parent->remove_child(item);
|
||||
}
|
||||
_get_result_root()->add_child(item);
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
|
||||
CanvasItem *prev_root = _get_result_root();
|
||||
|
||||
if (prev_root->is_visible() && content_display_mode == p_display_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
hide();
|
||||
content_display_mode = p_display_mode;
|
||||
CanvasItem *next_root = _get_result_root();
|
||||
|
||||
// Move result item nodes from one container to the other.
|
||||
CanvasItem *prev_root;
|
||||
CanvasItem *next_root;
|
||||
if (content_display_mode == QuickOpenDisplayMode::LIST) {
|
||||
prev_root = Object::cast_to<CanvasItem>(grid);
|
||||
next_root = Object::cast_to<CanvasItem>(list);
|
||||
} else {
|
||||
prev_root = Object::cast_to<CanvasItem>(list);
|
||||
next_root = Object::cast_to<CanvasItem>(grid);
|
||||
}
|
||||
|
||||
const bool first_time = !list->is_visible() && !grid->is_visible();
|
||||
EditorSettings::get_singleton()->set_project_metadata("quick_open_dialog", "last_mode", (int)content_display_mode);
|
||||
|
||||
prev_root->hide();
|
||||
for (QuickOpenResultItem *item : result_items) {
|
||||
item->set_display_mode(content_display_mode);
|
||||
|
||||
if (!first_time) {
|
||||
prev_root->remove_child(item);
|
||||
}
|
||||
|
||||
next_root->add_child(item);
|
||||
}
|
||||
next_root->show();
|
||||
show();
|
||||
|
||||
for (QuickOpenResultItem *item : result_items) {
|
||||
_layout_result_item(item);
|
||||
}
|
||||
|
||||
_update_result_items(num_visible_results, selection_index);
|
||||
|
||||
@ -627,16 +643,7 @@ bool QuickOpenResultContainer::has_nothing_selected() const {
|
||||
|
||||
String QuickOpenResultContainer::get_selected() const {
|
||||
ERR_FAIL_COND_V_MSG(has_nothing_selected(), String(), "Tried to get selected file, but nothing was selected.");
|
||||
|
||||
if (showing_history) {
|
||||
const List<Candidate> *type_history = selected_history.lookup_ptr(base_types[0]);
|
||||
|
||||
const Candidate &c = type_history->get(selection_index);
|
||||
return c.file_directory.path_join(c.file_name);
|
||||
} else {
|
||||
const Candidate &c = candidates[selection_index];
|
||||
return c.file_directory.path_join(c.file_name);
|
||||
}
|
||||
return candidates[selection_index].file_path;
|
||||
}
|
||||
|
||||
QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const Vector<StringName> &p_base_types) {
|
||||
@ -664,32 +671,27 @@ void QuickOpenResultContainer::save_selected_item() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showing_history) {
|
||||
// Selecting from history, so already added.
|
||||
return;
|
||||
}
|
||||
|
||||
const StringName &base_type = base_types[0];
|
||||
const QuickOpenResultCandidate &selected = candidates[selection_index];
|
||||
Vector<QuickOpenResultCandidate> *type_history = selected_history.lookup_ptr(base_type);
|
||||
|
||||
List<Candidate> *type_history = selected_history.lookup_ptr(base_type);
|
||||
if (!type_history) {
|
||||
selected_history.insert(base_type, List<Candidate>());
|
||||
selected_history.insert(base_type, Vector<QuickOpenResultCandidate>());
|
||||
type_history = selected_history.lookup_ptr(base_type);
|
||||
} else {
|
||||
const Candidate &selected = candidates[selection_index];
|
||||
|
||||
for (const Candidate &candidate : *type_history) {
|
||||
if (candidate.file_directory == selected.file_directory && candidate.file_name == selected.file_name) {
|
||||
return;
|
||||
for (int i = 0; i < type_history->size(); i++) {
|
||||
if (selected.file_path == type_history->get(i).file_path) {
|
||||
type_history->remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type_history->size() > 8) {
|
||||
type_history->pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
type_history->push_front(candidates[selection_index]);
|
||||
type_history->insert(0, selected);
|
||||
type_history->ptrw()->result = nullptr;
|
||||
if (type_history->size() > MAX_HISTORY_SIZE) {
|
||||
type_history->resize(MAX_HISTORY_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void QuickOpenResultContainer::cleanup() {
|
||||
@ -743,36 +745,35 @@ QuickOpenResultItem::QuickOpenResultItem() {
|
||||
void QuickOpenResultItem::set_display_mode(QuickOpenDisplayMode p_display_mode) {
|
||||
if (p_display_mode == QuickOpenDisplayMode::LIST) {
|
||||
grid_item->hide();
|
||||
grid_item->reset();
|
||||
list_item->show();
|
||||
} else {
|
||||
list_item->hide();
|
||||
list_item->reset();
|
||||
grid_item->show();
|
||||
}
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void QuickOpenResultItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
|
||||
void QuickOpenResultItem::set_content(const QuickOpenResultCandidate &p_candidate) {
|
||||
_set_enabled(true);
|
||||
|
||||
if (list_item->is_visible()) {
|
||||
list_item->set_content(p_thumbnail, p_file, p_file_directory);
|
||||
list_item->set_content(p_candidate, enable_highlights);
|
||||
} else {
|
||||
grid_item->set_content(p_thumbnail, p_file);
|
||||
grid_item->set_content(p_candidate, enable_highlights);
|
||||
}
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void QuickOpenResultItem::reset() {
|
||||
_set_enabled(false);
|
||||
|
||||
is_hovering = false;
|
||||
is_selected = false;
|
||||
|
||||
if (list_item->is_visible()) {
|
||||
list_item->reset();
|
||||
} else {
|
||||
grid_item->reset();
|
||||
}
|
||||
list_item->reset();
|
||||
grid_item->reset();
|
||||
}
|
||||
|
||||
void QuickOpenResultItem::highlight_item(bool p_enabled) {
|
||||
@ -825,6 +826,22 @@ void QuickOpenResultItem::_notification(int p_what) {
|
||||
|
||||
//----------------- List item
|
||||
|
||||
static Vector2i _get_path_interval(const Vector2i &p_interval, int p_dir_index) {
|
||||
if (p_interval.x >= p_dir_index || p_interval.y < 1) {
|
||||
return { -1, -1 };
|
||||
}
|
||||
return { p_interval.x, MIN(p_interval.x + p_interval.y, p_dir_index) - p_interval.x };
|
||||
}
|
||||
|
||||
static Vector2i _get_name_interval(const Vector2i &p_interval, int p_dir_index) {
|
||||
if (p_interval.x + p_interval.y <= p_dir_index || p_interval.y < 1) {
|
||||
return { -1, -1 };
|
||||
}
|
||||
int first_name_idx = p_dir_index + 1;
|
||||
int start = MAX(p_interval.x, first_name_idx);
|
||||
return { start - first_name_idx, p_interval.y - start + p_interval.x };
|
||||
}
|
||||
|
||||
QuickOpenResultListItem::QuickOpenResultListItem() {
|
||||
set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
add_theme_constant_override("separation", 4 * EDSCALE);
|
||||
@ -852,13 +869,13 @@ QuickOpenResultListItem::QuickOpenResultListItem() {
|
||||
text_container->set_v_size_flags(Control::SIZE_FILL);
|
||||
add_child(text_container);
|
||||
|
||||
name = memnew(Label);
|
||||
name = memnew(HighlightedLabel);
|
||||
name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT);
|
||||
text_container->add_child(name);
|
||||
|
||||
path = memnew(Label);
|
||||
path = memnew(HighlightedLabel);
|
||||
path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
path->add_theme_font_size_override(SceneStringName(font_size), 12 * EDSCALE);
|
||||
@ -866,18 +883,29 @@ QuickOpenResultListItem::QuickOpenResultListItem() {
|
||||
}
|
||||
}
|
||||
|
||||
void QuickOpenResultListItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
|
||||
thumbnail->set_texture(p_thumbnail);
|
||||
name->set_text(p_file);
|
||||
path->set_text(p_file_directory);
|
||||
void QuickOpenResultListItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
|
||||
thumbnail->set_texture(p_candidate.thumbnail);
|
||||
name->set_text(p_candidate.file_path.get_file());
|
||||
path->set_text(p_candidate.file_path.get_base_dir());
|
||||
name->reset_highlights();
|
||||
path->reset_highlights();
|
||||
|
||||
if (p_highlight && p_candidate.result != nullptr) {
|
||||
for (const FuzzyTokenMatch &match : p_candidate.result->token_matches) {
|
||||
for (const Vector2i &interval : match.substrings) {
|
||||
path->add_highlight(_get_path_interval(interval, p_candidate.result->dir_index));
|
||||
name->add_highlight(_get_name_interval(interval, p_candidate.result->dir_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int max_size = 32 * EDSCALE;
|
||||
bool uses_icon = p_thumbnail->get_width() < max_size;
|
||||
bool uses_icon = p_candidate.thumbnail->get_width() < max_size;
|
||||
|
||||
if (uses_icon) {
|
||||
thumbnail->set_custom_minimum_size(p_thumbnail->get_size());
|
||||
thumbnail->set_custom_minimum_size(p_candidate.thumbnail->get_size());
|
||||
|
||||
int margin_needed = (max_size - p_thumbnail->get_width()) / 2;
|
||||
int margin_needed = (max_size - p_candidate.thumbnail->get_width()) / 2;
|
||||
image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN + margin_needed);
|
||||
image_container->add_theme_constant_override("margin_right", margin_needed);
|
||||
} else {
|
||||
@ -888,9 +916,11 @@ void QuickOpenResultListItem::set_content(const Ref<Texture2D> &p_thumbnail, con
|
||||
}
|
||||
|
||||
void QuickOpenResultListItem::reset() {
|
||||
name->set_text("");
|
||||
thumbnail->set_texture(nullptr);
|
||||
name->set_text("");
|
||||
path->set_text("");
|
||||
name->reset_highlights();
|
||||
path->reset_highlights();
|
||||
}
|
||||
|
||||
void QuickOpenResultListItem::highlight_item(const Color &p_color) {
|
||||
@ -919,10 +949,10 @@ QuickOpenResultGridItem::QuickOpenResultGridItem() {
|
||||
thumbnail = memnew(TextureRect);
|
||||
thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
thumbnail->set_custom_minimum_size(Size2i(80 * EDSCALE, 64 * EDSCALE));
|
||||
thumbnail->set_custom_minimum_size(Size2i(120 * EDSCALE, 64 * EDSCALE));
|
||||
add_child(thumbnail);
|
||||
|
||||
name = memnew(Label);
|
||||
name = memnew(HighlightedLabel);
|
||||
name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
|
||||
@ -930,16 +960,23 @@ QuickOpenResultGridItem::QuickOpenResultGridItem() {
|
||||
add_child(name);
|
||||
}
|
||||
|
||||
void QuickOpenResultGridItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file) {
|
||||
thumbnail->set_texture(p_thumbnail);
|
||||
void QuickOpenResultGridItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
|
||||
thumbnail->set_texture(p_candidate.thumbnail);
|
||||
name->set_text(p_candidate.file_path.get_file());
|
||||
name->set_tooltip_text(p_candidate.file_path);
|
||||
name->reset_highlights();
|
||||
|
||||
const String &file_name = p_file.get_basename();
|
||||
name->set_text(file_name);
|
||||
name->set_tooltip_text(file_name);
|
||||
if (p_highlight && p_candidate.result != nullptr) {
|
||||
for (const FuzzyTokenMatch &match : p_candidate.result->token_matches) {
|
||||
for (const Vector2i &interval : match.substrings) {
|
||||
name->add_highlight(_get_name_interval(interval, p_candidate.result->dir_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool uses_icon = p_thumbnail->get_width() < (32 * EDSCALE);
|
||||
bool uses_icon = p_candidate.thumbnail->get_width() < (32 * EDSCALE);
|
||||
|
||||
if (uses_icon || p_thumbnail->get_height() <= thumbnail->get_custom_minimum_size().y) {
|
||||
if (uses_icon || p_candidate.thumbnail->get_height() <= thumbnail->get_custom_minimum_size().y) {
|
||||
thumbnail->set_expand_mode(TextureRect::EXPAND_KEEP_SIZE);
|
||||
thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED);
|
||||
} else {
|
||||
@ -949,8 +986,9 @@ void QuickOpenResultGridItem::set_content(const Ref<Texture2D> &p_thumbnail, con
|
||||
}
|
||||
|
||||
void QuickOpenResultGridItem::reset() {
|
||||
name->set_text("");
|
||||
thumbnail->set_texture(nullptr);
|
||||
name->set_text("");
|
||||
name->reset_highlights();
|
||||
}
|
||||
|
||||
void QuickOpenResultGridItem::highlight_item(const Color &p_color) {
|
||||
|
@ -48,6 +48,8 @@ class Texture2D;
|
||||
class TextureRect;
|
||||
class VBoxContainer;
|
||||
|
||||
class FuzzySearchResult;
|
||||
|
||||
class QuickOpenResultItem;
|
||||
|
||||
enum class QuickOpenDisplayMode {
|
||||
@ -55,13 +57,35 @@ enum class QuickOpenDisplayMode {
|
||||
LIST,
|
||||
};
|
||||
|
||||
struct QuickOpenResultCandidate {
|
||||
String file_path;
|
||||
Ref<Texture2D> thumbnail;
|
||||
const FuzzySearchResult *result = nullptr;
|
||||
};
|
||||
|
||||
class HighlightedLabel : public Label {
|
||||
GDCLASS(HighlightedLabel, Label)
|
||||
|
||||
Vector<Vector2i> highlights;
|
||||
|
||||
void draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing);
|
||||
|
||||
public:
|
||||
void add_highlight(const Vector2i &p_interval);
|
||||
void reset_highlights();
|
||||
|
||||
protected:
|
||||
void _notification(int p_notification);
|
||||
};
|
||||
|
||||
class QuickOpenResultContainer : public VBoxContainer {
|
||||
GDCLASS(QuickOpenResultContainer, VBoxContainer)
|
||||
|
||||
public:
|
||||
void init(const Vector<StringName> &p_base_types);
|
||||
void handle_search_box_input(const Ref<InputEvent> &p_ie);
|
||||
void update_results(const String &p_query);
|
||||
void set_query_and_update(const String &p_query);
|
||||
void update_results();
|
||||
|
||||
bool has_nothing_selected() const;
|
||||
String get_selected() const;
|
||||
@ -70,27 +94,21 @@ public:
|
||||
void cleanup();
|
||||
|
||||
QuickOpenResultContainer();
|
||||
~QuickOpenResultContainer();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
private:
|
||||
static const int TOTAL_ALLOCATED_RESULT_ITEMS = 100;
|
||||
static const int SHOW_ALL_FILES_THRESHOLD = 30;
|
||||
|
||||
struct Candidate {
|
||||
String file_name;
|
||||
String file_directory;
|
||||
|
||||
Ref<Texture2D> thumbnail;
|
||||
float score = 0;
|
||||
};
|
||||
static constexpr int SHOW_ALL_FILES_THRESHOLD = 30;
|
||||
static constexpr int MAX_HISTORY_SIZE = 20;
|
||||
|
||||
Vector<FuzzySearchResult> search_results;
|
||||
Vector<StringName> base_types;
|
||||
Vector<Candidate> candidates;
|
||||
Vector<String> filepaths;
|
||||
OAHashMap<String, StringName> filetypes;
|
||||
Vector<QuickOpenResultCandidate> candidates;
|
||||
|
||||
OAHashMap<StringName, List<Candidate>> selected_history;
|
||||
OAHashMap<StringName, Vector<QuickOpenResultCandidate>> selected_history;
|
||||
|
||||
String query;
|
||||
int selection_index = -1;
|
||||
@ -114,15 +132,21 @@ private:
|
||||
Label *file_details_path = nullptr;
|
||||
Button *display_mode_toggle = nullptr;
|
||||
CheckButton *include_addons_toggle = nullptr;
|
||||
CheckButton *fuzzy_search_toggle = nullptr;
|
||||
|
||||
OAHashMap<StringName, Ref<Texture2D>> file_type_icons;
|
||||
|
||||
static QuickOpenDisplayMode get_adaptive_display_mode(const Vector<StringName> &p_base_types);
|
||||
|
||||
void _create_initial_results(bool p_include_addons);
|
||||
void _find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
|
||||
void _ensure_result_vector_capacity();
|
||||
void _create_initial_results();
|
||||
void _find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
|
||||
|
||||
int _sort_candidates(const String &p_query);
|
||||
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const String &p_filepath);
|
||||
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result);
|
||||
void _update_fuzzy_search_results();
|
||||
void _use_default_candidates();
|
||||
void _score_and_sort_candidates();
|
||||
void _update_result_items(int p_new_visible_results_count, int p_new_selection_index);
|
||||
|
||||
void _move_selection_index(Key p_key);
|
||||
@ -130,9 +154,12 @@ private:
|
||||
|
||||
void _item_input(const Ref<InputEvent> &p_ev, int p_index);
|
||||
|
||||
CanvasItem *_get_result_root();
|
||||
void _layout_result_item(QuickOpenResultItem *p_item);
|
||||
void _set_display_mode(QuickOpenDisplayMode p_display_mode);
|
||||
void _toggle_display_mode();
|
||||
void _toggle_include_addons(bool p_pressed);
|
||||
void _toggle_fuzzy_search(bool p_pressed);
|
||||
|
||||
static void _bind_methods();
|
||||
};
|
||||
@ -143,14 +170,14 @@ class QuickOpenResultGridItem : public VBoxContainer {
|
||||
public:
|
||||
QuickOpenResultGridItem();
|
||||
|
||||
void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name);
|
||||
void reset();
|
||||
void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
|
||||
void highlight_item(const Color &p_color);
|
||||
void remove_highlight();
|
||||
|
||||
private:
|
||||
TextureRect *thumbnail = nullptr;
|
||||
Label *name = nullptr;
|
||||
HighlightedLabel *name = nullptr;
|
||||
};
|
||||
|
||||
class QuickOpenResultListItem : public HBoxContainer {
|
||||
@ -159,8 +186,8 @@ class QuickOpenResultListItem : public HBoxContainer {
|
||||
public:
|
||||
QuickOpenResultListItem();
|
||||
|
||||
void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
|
||||
void reset();
|
||||
void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
|
||||
void highlight_item(const Color &p_color);
|
||||
void remove_highlight();
|
||||
|
||||
@ -174,8 +201,8 @@ private:
|
||||
VBoxContainer *text_container = nullptr;
|
||||
|
||||
TextureRect *thumbnail = nullptr;
|
||||
Label *name = nullptr;
|
||||
Label *path = nullptr;
|
||||
HighlightedLabel *name = nullptr;
|
||||
HighlightedLabel *path = nullptr;
|
||||
};
|
||||
|
||||
class QuickOpenResultItem : public HBoxContainer {
|
||||
@ -184,10 +211,11 @@ class QuickOpenResultItem : public HBoxContainer {
|
||||
public:
|
||||
QuickOpenResultItem();
|
||||
|
||||
void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
|
||||
void set_display_mode(QuickOpenDisplayMode p_display_mode);
|
||||
void reset();
|
||||
bool enable_highlights = true;
|
||||
|
||||
void reset();
|
||||
void set_content(const QuickOpenResultCandidate &p_candidate);
|
||||
void set_display_mode(QuickOpenDisplayMode p_display_mode);
|
||||
void highlight_item(bool p_enabled);
|
||||
|
||||
protected:
|
||||
|
@ -335,6 +335,121 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
|
||||
}
|
||||
}
|
||||
|
||||
void Label::_ensure_shaped() const {
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
}
|
||||
}
|
||||
|
||||
RID Label::get_line_rid(int p_line) const {
|
||||
return lines_rid[p_line];
|
||||
}
|
||||
|
||||
Rect2 Label::get_line_rect(int p_line) const {
|
||||
// Returns a rect providing the line's horizontal offset and total size. To determine the vertical
|
||||
// offset, use r_offset and r_line_spacing from get_layout_data.
|
||||
bool rtl = TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL;
|
||||
bool rtl_layout = is_layout_rtl();
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
Size2 size = get_size();
|
||||
Size2 line_size = TS->shaped_text_get_size(lines_rid[p_line]);
|
||||
Vector2 offset;
|
||||
|
||||
switch (horizontal_alignment) {
|
||||
case HORIZONTAL_ALIGNMENT_FILL:
|
||||
if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
|
||||
offset.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
} else {
|
||||
offset.x = style->get_offset().x;
|
||||
}
|
||||
break;
|
||||
case HORIZONTAL_ALIGNMENT_LEFT: {
|
||||
if (rtl_layout) {
|
||||
offset.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
} else {
|
||||
offset.x = style->get_offset().x;
|
||||
}
|
||||
} break;
|
||||
case HORIZONTAL_ALIGNMENT_CENTER: {
|
||||
offset.x = int(size.width - line_size.width) / 2;
|
||||
} break;
|
||||
case HORIZONTAL_ALIGNMENT_RIGHT: {
|
||||
if (rtl_layout) {
|
||||
offset.x = style->get_offset().x;
|
||||
} else {
|
||||
offset.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
return Rect2(offset, line_size);
|
||||
}
|
||||
|
||||
void Label::get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_spacing) const {
|
||||
// Computes several common parameters involved in laying out and rendering text set to this label.
|
||||
// Only vertical margin is considered in r_offset: use get_line_rect to get the horizontal offset
|
||||
// for a given line of text.
|
||||
Size2 size = get_size();
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing;
|
||||
|
||||
float total_h = 0.0;
|
||||
int lines_visible = 0;
|
||||
|
||||
// Get number of lines to fit to the height.
|
||||
for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
|
||||
break;
|
||||
}
|
||||
lines_visible++;
|
||||
}
|
||||
|
||||
if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
|
||||
lines_visible = max_lines_visible;
|
||||
}
|
||||
|
||||
r_line_limit = MIN(lines_rid.size(), lines_visible + lines_skipped);
|
||||
|
||||
// Get real total height.
|
||||
total_h = 0;
|
||||
for (int64_t i = lines_skipped; i < r_line_limit; i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
}
|
||||
total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
|
||||
|
||||
int vbegin = 0, vsep = 0;
|
||||
if (lines_visible > 0) {
|
||||
switch (vertical_alignment) {
|
||||
case VERTICAL_ALIGNMENT_TOP: {
|
||||
// Nothing.
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_CENTER: {
|
||||
vbegin = (size.y - (total_h - line_spacing)) / 2;
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_BOTTOM: {
|
||||
vbegin = size.y - (total_h - line_spacing);
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_FILL: {
|
||||
vbegin = 0;
|
||||
if (lines_visible > 1) {
|
||||
vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
|
||||
} else {
|
||||
vsep = 0;
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
r_offset = { 0, style->get_offset().y + vbegin };
|
||||
r_line_spacing = line_spacing + vsep;
|
||||
}
|
||||
|
||||
PackedStringArray Label::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Control::get_configuration_warnings();
|
||||
|
||||
@ -361,10 +476,7 @@ PackedStringArray Label::get_configuration_warnings() const {
|
||||
}
|
||||
|
||||
if (font.is_valid()) {
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
}
|
||||
|
||||
_ensure_shaped();
|
||||
const Glyph *glyph = TS->shaped_text_get_glyphs(text_rid);
|
||||
int64_t glyph_count = TS->shaped_text_get_glyph_count(text_rid);
|
||||
for (int64_t i = 0; i < glyph_count; i++) {
|
||||
@ -416,22 +528,17 @@ void Label::_notification(int p_what) {
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
_shape();
|
||||
}
|
||||
_ensure_shaped();
|
||||
|
||||
RID ci = get_canvas_item();
|
||||
|
||||
bool has_settings = settings.is_valid();
|
||||
|
||||
Size2 string_size;
|
||||
Size2 size = get_size();
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
|
||||
Color font_color = has_settings ? settings->get_font_color() : theme_cache.font_color;
|
||||
Color font_shadow_color = has_settings ? settings->get_shadow_color() : theme_cache.font_shadow_color;
|
||||
Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : theme_cache.font_shadow_offset;
|
||||
int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing;
|
||||
Color font_outline_color = has_settings ? settings->get_outline_color() : theme_cache.font_outline_color;
|
||||
int outline_size = has_settings ? settings->get_outline_size() : theme_cache.font_outline_size;
|
||||
int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size;
|
||||
@ -440,98 +547,28 @@ void Label::_notification(int p_what) {
|
||||
|
||||
style->draw(ci, Rect2(Point2(0, 0), get_size()));
|
||||
|
||||
float total_h = 0.0;
|
||||
int lines_visible = 0;
|
||||
|
||||
// Get number of lines to fit to the height.
|
||||
for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
|
||||
break;
|
||||
}
|
||||
lines_visible++;
|
||||
}
|
||||
|
||||
if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
|
||||
lines_visible = max_lines_visible;
|
||||
}
|
||||
|
||||
int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
|
||||
bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING);
|
||||
bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !rtl_layout));
|
||||
bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && rtl_layout));
|
||||
|
||||
// Get real total height.
|
||||
Vector2 ofs;
|
||||
int line_limit;
|
||||
int line_spacing;
|
||||
get_layout_data(ofs, line_limit, line_spacing);
|
||||
|
||||
int processed_glyphs = 0;
|
||||
int total_glyphs = 0;
|
||||
total_h = 0;
|
||||
for (int64_t i = lines_skipped; i < last_line; i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
|
||||
for (int64_t i = lines_skipped; i < line_limit; i++) {
|
||||
total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
|
||||
}
|
||||
|
||||
int visible_glyphs = total_glyphs * visible_ratio;
|
||||
int processed_glyphs = 0;
|
||||
total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
|
||||
|
||||
int vbegin = 0, vsep = 0;
|
||||
if (lines_visible > 0) {
|
||||
switch (vertical_alignment) {
|
||||
case VERTICAL_ALIGNMENT_TOP: {
|
||||
// Nothing.
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_CENTER: {
|
||||
vbegin = (size.y - (total_h - line_spacing)) / 2;
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_BOTTOM: {
|
||||
vbegin = size.y - (total_h - line_spacing);
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_FILL: {
|
||||
vbegin = 0;
|
||||
if (lines_visible > 1) {
|
||||
vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
|
||||
} else {
|
||||
vsep = 0;
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 ofs;
|
||||
ofs.y = style->get_offset().y + vbegin;
|
||||
for (int i = lines_skipped; i < last_line; i++) {
|
||||
Size2 line_size = TS->shaped_text_get_size(lines_rid[i]);
|
||||
ofs.x = 0;
|
||||
for (int i = lines_skipped; i < line_limit; i++) {
|
||||
Vector2 line_offset = get_line_rect(i).position;
|
||||
ofs.x = line_offset.x;
|
||||
ofs.y += TS->shaped_text_get_ascent(lines_rid[i]);
|
||||
switch (horizontal_alignment) {
|
||||
case HORIZONTAL_ALIGNMENT_FILL:
|
||||
if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
|
||||
ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
} else {
|
||||
ofs.x = style->get_offset().x;
|
||||
}
|
||||
break;
|
||||
case HORIZONTAL_ALIGNMENT_LEFT: {
|
||||
if (rtl_layout) {
|
||||
ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
} else {
|
||||
ofs.x = style->get_offset().x;
|
||||
}
|
||||
} break;
|
||||
case HORIZONTAL_ALIGNMENT_CENTER: {
|
||||
ofs.x = int(size.width - line_size.width) / 2;
|
||||
} break;
|
||||
case HORIZONTAL_ALIGNMENT_RIGHT: {
|
||||
if (rtl_layout) {
|
||||
ofs.x = style->get_offset().x;
|
||||
} else {
|
||||
ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
|
||||
int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
|
||||
@ -621,7 +658,7 @@ void Label::_notification(int p_what) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing;
|
||||
ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
|
||||
}
|
||||
} break;
|
||||
|
||||
@ -637,102 +674,16 @@ void Label::_notification(int p_what) {
|
||||
}
|
||||
|
||||
Rect2 Label::get_character_bounds(int p_pos) const {
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
}
|
||||
|
||||
bool has_settings = settings.is_valid();
|
||||
Size2 size = get_size();
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing;
|
||||
bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL);
|
||||
bool rtl_layout = is_layout_rtl();
|
||||
|
||||
float total_h = 0.0;
|
||||
int lines_visible = 0;
|
||||
|
||||
// Get number of lines to fit to the height.
|
||||
for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
|
||||
break;
|
||||
}
|
||||
lines_visible++;
|
||||
}
|
||||
|
||||
if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
|
||||
lines_visible = max_lines_visible;
|
||||
}
|
||||
|
||||
int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
|
||||
|
||||
// Get real total height.
|
||||
total_h = 0;
|
||||
for (int64_t i = lines_skipped; i < last_line; i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
}
|
||||
|
||||
total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
|
||||
|
||||
int vbegin = 0, vsep = 0;
|
||||
if (lines_visible > 0) {
|
||||
switch (vertical_alignment) {
|
||||
case VERTICAL_ALIGNMENT_TOP: {
|
||||
// Nothing.
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_CENTER: {
|
||||
vbegin = (size.y - (total_h - line_spacing)) / 2;
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_BOTTOM: {
|
||||
vbegin = size.y - (total_h - line_spacing);
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_FILL: {
|
||||
vbegin = 0;
|
||||
if (lines_visible > 1) {
|
||||
vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
|
||||
} else {
|
||||
vsep = 0;
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
_ensure_shaped();
|
||||
|
||||
Vector2 ofs;
|
||||
ofs.y = style->get_offset().y + vbegin;
|
||||
for (int i = lines_skipped; i < last_line; i++) {
|
||||
Size2 line_size = TS->shaped_text_get_size(lines_rid[i]);
|
||||
ofs.x = 0;
|
||||
switch (horizontal_alignment) {
|
||||
case HORIZONTAL_ALIGNMENT_FILL:
|
||||
if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
|
||||
ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
} else {
|
||||
ofs.x = style->get_offset().x;
|
||||
}
|
||||
break;
|
||||
case HORIZONTAL_ALIGNMENT_LEFT: {
|
||||
if (rtl_layout) {
|
||||
ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
} else {
|
||||
ofs.x = style->get_offset().x;
|
||||
}
|
||||
} break;
|
||||
case HORIZONTAL_ALIGNMENT_CENTER: {
|
||||
ofs.x = int(size.width - line_size.width) / 2;
|
||||
} break;
|
||||
case HORIZONTAL_ALIGNMENT_RIGHT: {
|
||||
if (rtl_layout) {
|
||||
ofs.x = style->get_offset().x;
|
||||
} else {
|
||||
ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
int line_limit;
|
||||
int line_spacing;
|
||||
get_layout_data(ofs, line_limit, line_spacing);
|
||||
|
||||
for (int i = lines_skipped; i < line_limit; i++) {
|
||||
Rect2 line_rect = get_line_rect(i);
|
||||
ofs.x = line_rect.position.x;
|
||||
int v_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
|
||||
|
||||
@ -746,22 +697,19 @@ Rect2 Label::get_character_bounds(int p_pos) const {
|
||||
}
|
||||
Rect2 rect;
|
||||
rect.position = ofs + Vector2(gl_off, 0);
|
||||
rect.size = Vector2(advance, TS->shaped_text_get_size(lines_rid[i]).y);
|
||||
rect.size = Vector2(advance, line_rect.size.y);
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
gl_off += glyphs[j].advance * glyphs[j].repeat;
|
||||
}
|
||||
ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing;
|
||||
ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
|
||||
}
|
||||
return Rect2();
|
||||
}
|
||||
|
||||
Size2 Label::get_minimum_size() const {
|
||||
// don't want to mutable everything
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
}
|
||||
_ensure_shaped();
|
||||
|
||||
Size2 min_size = minsize;
|
||||
|
||||
@ -798,10 +746,7 @@ int Label::get_line_count() const {
|
||||
if (!is_inside_tree()) {
|
||||
return 1;
|
||||
}
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
}
|
||||
|
||||
_ensure_shaped();
|
||||
return lines_rid.size();
|
||||
}
|
||||
|
||||
@ -1104,10 +1049,7 @@ int Label::get_max_lines_visible() const {
|
||||
}
|
||||
|
||||
int Label::get_total_character_count() const {
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
}
|
||||
|
||||
_ensure_shaped();
|
||||
return xl_text.length();
|
||||
}
|
||||
|
||||
|
@ -91,11 +91,16 @@ private:
|
||||
int font_shadow_outline_size;
|
||||
} theme_cache;
|
||||
|
||||
void _ensure_shaped() const;
|
||||
void _update_visible();
|
||||
void _shape();
|
||||
void _invalidate();
|
||||
|
||||
protected:
|
||||
RID get_line_rid(int p_line) const;
|
||||
Rect2 get_line_rect(int p_line) const;
|
||||
void get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_spacing) const;
|
||||
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
83
tests/core/string/test_fuzzy_search.h
Normal file
83
tests/core/string/test_fuzzy_search.h
Normal file
@ -0,0 +1,83 @@
|
||||
/**************************************************************************/
|
||||
/* test_fuzzy_search.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_FUZZY_SEARCH_H
|
||||
#define TEST_FUZZY_SEARCH_H
|
||||
|
||||
#include "core/string/fuzzy_search.h"
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestFuzzySearch {
|
||||
|
||||
struct FuzzySearchTestCase {
|
||||
String query;
|
||||
String expected;
|
||||
};
|
||||
|
||||
// Ideally each of these test queries should represent a different aspect, and potentially bottleneck, of the search process.
|
||||
const FuzzySearchTestCase test_cases[] = {
|
||||
// Short query, many matches, few adjacent characters
|
||||
{ "///gd", "./menu/hud/hud.gd" },
|
||||
// Filename match with typo
|
||||
{ "sm.png", "./entity/blood_sword/sam.png" },
|
||||
// Multipart filename word matches
|
||||
{ "ham ", "./entity/game_trap/ha_missed_me.wav" },
|
||||
// Single word token matches
|
||||
{ "push background", "./entity/background_zone1/background/push.png" },
|
||||
// Long token matches
|
||||
{ "background_freighter background png", "./entity/background_freighter/background/background.png" },
|
||||
// Many matches, many short tokens
|
||||
{ "menu menu characters wav", "./menu/menu/characters/smoker/0.wav" },
|
||||
// Maximize total matches
|
||||
{ "entity gd", "./entity/entity_man.gd" }
|
||||
};
|
||||
|
||||
Vector<String> load_test_data() {
|
||||
Ref<FileAccess> fp = FileAccess::open(TestUtils::get_data_path("fuzzy_search/project_dir_tree.txt"), FileAccess::READ);
|
||||
REQUIRE(fp.is_valid());
|
||||
return fp->get_as_utf8_string().split("\n");
|
||||
}
|
||||
|
||||
TEST_CASE("[FuzzySearch] Test fuzzy search results") {
|
||||
FuzzySearch search;
|
||||
Vector<FuzzySearchResult> results;
|
||||
Vector<String> targets = load_test_data();
|
||||
|
||||
for (FuzzySearchTestCase test_case : test_cases) {
|
||||
search.set_query(test_case.query);
|
||||
search.search_all(targets, results);
|
||||
CHECK_GT(results.size(), 0);
|
||||
CHECK_EQ(results[0].target, test_case.expected);
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestFuzzySearch
|
||||
|
||||
#endif // TEST_FUZZY_SEARCH_H
|
@ -389,6 +389,19 @@ TEST_CASE("[String] Find") {
|
||||
MULTICHECK_STRING_INT_EQ(s, rfind, "", 15, -1);
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Find character") {
|
||||
String s = "racecar";
|
||||
CHECK_EQ(s.find_char('r'), 0);
|
||||
CHECK_EQ(s.find_char('r', 1), 6);
|
||||
CHECK_EQ(s.find_char('e'), 3);
|
||||
CHECK_EQ(s.find_char('e', 4), -1);
|
||||
|
||||
CHECK_EQ(s.rfind_char('r'), 6);
|
||||
CHECK_EQ(s.rfind_char('r', 5), 0);
|
||||
CHECK_EQ(s.rfind_char('e'), 3);
|
||||
CHECK_EQ(s.rfind_char('e', 2), -1);
|
||||
}
|
||||
|
||||
TEST_CASE("[String] Find case insensitive") {
|
||||
String s = "Pretty Whale Whale";
|
||||
MULTICHECK_STRING_EQ(s, findn, "WHA", 7);
|
||||
@ -1254,6 +1267,12 @@ TEST_CASE("[String] is_subsequence_of") {
|
||||
CHECK(String("Sub").is_subsequence_ofn(a));
|
||||
}
|
||||
|
||||
TEST_CASE("[String] is_lowercase") {
|
||||
CHECK(String("abcd1234 !@#$%^&*()_-=+,.<>/\\|[]{};':\"`~").is_lowercase());
|
||||
CHECK(String("").is_lowercase());
|
||||
CHECK(!String("abc_ABC").is_lowercase());
|
||||
}
|
||||
|
||||
TEST_CASE("[String] match") {
|
||||
CHECK(String("img1.png").match("*.png"));
|
||||
CHECK(!String("img1.jpeg").match("*.png"));
|
||||
|
999
tests/data/fuzzy_search/project_dir_tree.txt
Normal file
999
tests/data/fuzzy_search/project_dir_tree.txt
Normal file
@ -0,0 +1,999 @@
|
||||
./menu/home/home_menu.tscn
|
||||
./menu/tooltips/tooltip_server.tscn
|
||||
./menu/tooltips/tooltip_server.gd
|
||||
./menu/tooltips/tooltip.gd
|
||||
./menu/menu/characters/smoker/4.wav
|
||||
./menu/menu/characters/smoker/6.wav
|
||||
./menu/menu/characters/smoker/10.wav
|
||||
./menu/menu/characters/smoker/smoker.tscn
|
||||
./menu/menu/characters/smoker/8.wav
|
||||
./menu/menu/characters/smoker/type.gd
|
||||
./menu/menu/characters/smoker/9.wav
|
||||
./menu/menu/characters/smoker/5.wav
|
||||
./menu/menu/characters/smoker/0.wav
|
||||
./menu/menu/characters/smoker/back_light.png
|
||||
./menu/menu/characters/smoker/glasses.png
|
||||
./menu/menu/characters/smoker/smoker.gd
|
||||
./menu/menu/characters/smoker/cig.gd
|
||||
./menu/menu/characters/smoker/eyes.png
|
||||
./menu/menu/characters/smoker/3.wav
|
||||
./menu/menu/characters/smoker/to_pixelate.gd
|
||||
./menu/menu/characters/smoker/7.wav
|
||||
./menu/menu/characters/smoker/cig.png
|
||||
./menu/menu/characters/smoker/2.wav
|
||||
./menu/menu/characters/smoker/1.wav
|
||||
./menu/menu/characters/smoke.png
|
||||
./menu/menu/characters/space_bandit.tres
|
||||
./menu/menu/characters/dead_guy/blood_texture.png
|
||||
./menu/menu/characters/dead_guy/head_gibbed.png
|
||||
./menu/menu/characters/dead_guy/back_light.png
|
||||
./menu/menu/characters/dead_guy/smoker.gd
|
||||
./menu/menu/characters/dead_guy/eyes.png
|
||||
./menu/menu/characters/dead_guy/to_pixelate.gd
|
||||
./menu/menu/characters/dead_guy/dead_guy.gd
|
||||
./menu/menu/characters/dead_guy/eyes.gd
|
||||
./menu/menu/characters/dead_guy/x.png
|
||||
./menu/menu/characters/dead_guy/dead_guy.tscn
|
||||
./menu/menu/characters/dead_guy/mouth.png
|
||||
./menu/menu/characters/dead_guy/dead_guy.tres
|
||||
./menu/menu/characters/Label.gd
|
||||
./menu/menu/characters/guns2.png
|
||||
./menu/menu/characters/c.gd
|
||||
./menu/menu/characters/smoke.gd
|
||||
./menu/menu/characters/character.gd
|
||||
./menu/menu/characters/space_bandit/eyes.tres
|
||||
./menu/menu/characters/space_bandit/space_bandit_face_happy.png
|
||||
./menu/menu/characters/space_bandit/space_bandit.gd
|
||||
./menu/menu/characters/space_bandit/space_bandit.tscn
|
||||
./menu/menu/characters/boss/smoker.tscn
|
||||
./menu/menu/characters/boss/back_light.png
|
||||
./menu/menu/characters/boss/glasses.png
|
||||
./menu/menu/characters/boss/smoker.gd
|
||||
./menu/menu/characters/boss/cig.gd
|
||||
./menu/menu/characters/boss/eyes.png
|
||||
./menu/menu/characters/boss/to_pixelate.gd
|
||||
./menu/menu/characters/boss/x.png
|
||||
./menu/menu/characters/boss/cig.png
|
||||
./menu/menu/characters/eye.gd
|
||||
./menu/menu/characters/space_bandit_face_happy.png
|
||||
./menu/menu/characters/face.gd
|
||||
./menu/menu/characters/color.tres
|
||||
./menu/menu/characters/space_bandit.tscn
|
||||
./menu/menu/characters/space_bandit_face_bloody.png
|
||||
./menu/menu/characters/guns.png
|
||||
./menu/menu/characters/eyes2.tres
|
||||
./menu/options/controls/use.tres
|
||||
./menu/options/controls/input_map_button.gd
|
||||
./menu/options/controls/swap.tres
|
||||
./menu/options/controls/teleport.tres
|
||||
./menu/options/controls/joy_controls.tscn
|
||||
./menu/options/controls/mouse_and_keyboard_controls.tscn
|
||||
./menu/options/controls/input_map_button.tscn
|
||||
./menu/options/controls/special.tres
|
||||
./menu/options/controls/throw.tres
|
||||
./menu/options/controls/center.tres
|
||||
./menu/options/controls/input_action.gd
|
||||
./menu/options/controls/move.tres
|
||||
./menu/options/controls/melee.tres
|
||||
./menu/options/controls/controls.gd
|
||||
./menu/options/options.gd
|
||||
./menu/options/options.tscn
|
||||
./menu/options/graphics/graphics.tscn
|
||||
./menu/options/graphics/graphics.gd
|
||||
./menu/options/audio/audio.gd
|
||||
./menu/options/audio/audio.tscn
|
||||
./menu/options/game/game.gd
|
||||
./menu/options/game/game.tscn
|
||||
./menu/circle.tres
|
||||
./menu/fonts/keys.png
|
||||
./menu/fonts/rainbow_font.tres
|
||||
./menu/fonts/fallback_font.tres
|
||||
./menu/fonts/taxi_Driver.png
|
||||
./menu/fonts/NotoSansJP-Regular.ttf
|
||||
./menu/fonts/taxi_Driver_noise.png
|
||||
./menu/fonts/rainbow_font_shader.tres
|
||||
./menu/fonts/m5x7.ttf
|
||||
./menu/colors.gd
|
||||
./menu/toast_enter.wav
|
||||
./menu/ui_colors.tres
|
||||
./menu/pause/pause.gd
|
||||
./menu/pause/rainbow.tres
|
||||
./menu/pause/Label.gd
|
||||
./menu/pause/label.tscn
|
||||
./menu/pause/pause.tscn
|
||||
./menu/hoola.wav
|
||||
./menu/in_game_fallback.tres
|
||||
./menu/widgets/next_unlock.gd
|
||||
./menu/widgets/slider.gd
|
||||
./menu/widgets/fade.tscn
|
||||
./menu/widgets/background_hint.gd
|
||||
./menu/widgets/panel_container_smoke.gd
|
||||
./menu/widgets/wishlist_sticker.gd
|
||||
./menu/widgets/smoke.tres
|
||||
./menu/widgets/color_grade.gd
|
||||
./menu/widgets/rich_text_button.gd
|
||||
./menu/widgets/panel_container_smok2.tscn
|
||||
./menu/widgets/slider.tscn
|
||||
./menu/widgets/rich_text_heading.gd
|
||||
./menu/widgets/background_hint.tscn
|
||||
./menu/widgets/tip.tscn
|
||||
./menu/widgets/rich_text_button.tscn
|
||||
./menu/widgets/toggle.tscn
|
||||
./menu/widgets/heading.tscn
|
||||
./menu/widgets/hover.tscn
|
||||
./menu/widgets/toggle.gd
|
||||
./menu/widgets/smoke_panel_material.tres
|
||||
./menu/widgets/confirm.gd
|
||||
./menu/widgets/tip.gd
|
||||
./menu/widgets/panel.gd
|
||||
./menu/widgets/modal.gd
|
||||
./menu/widgets/NinePatchRect.gd
|
||||
./menu/widgets/smoke.shader
|
||||
./menu/widgets/9patch.png
|
||||
./menu/widgets/big_hint.gd
|
||||
./menu/widgets/TDVB1i.png
|
||||
./menu/widgets/color_grade.tscn
|
||||
./menu/widgets/text.gd
|
||||
./menu/widgets/panel_container_smoke.tscn
|
||||
./menu/widgets/1x1.png
|
||||
./menu/widgets/confirm.tscn
|
||||
./menu/widgets/RichTextPanel.tscn
|
||||
./menu/hud/cursor.png
|
||||
./menu/hud/inventory/draggable.gd
|
||||
./menu/hud/inventory/menu/characters/color.tres
|
||||
./menu/hud/inventory/drop_zone.tscn
|
||||
./menu/hud/inventory/RichTextLabel.gd
|
||||
./menu/hud/inventory/hud_icon_mutation.tscn
|
||||
./menu/hud/inventory/use_count.gd
|
||||
./menu/hud/inventory/draggable.tscn
|
||||
./menu/hud/inventory/black_shadow_font.tres
|
||||
./menu/hud/inventory/x.png
|
||||
./menu/hud/inventory/hud_icon_mutation.gd
|
||||
./menu/hud/inventory/flash_parent.gd
|
||||
./menu/hud/inventory/TextureRect4.gd
|
||||
./menu/hud/cursor.tscn
|
||||
./menu/hud/hud.tscn
|
||||
./menu/hud/cursor.gd
|
||||
./menu/hud/hud.gd
|
||||
./menu/metal_text.tres
|
||||
./menu/rich_text_effects/RichTextType.gd
|
||||
./menu/rich_text_effects/RichTextPanel.gd
|
||||
./menu/rich_text_effects/RichTextFlash.gd
|
||||
./menu/rich_text_effects/RichTextTranslate.gd
|
||||
./menu/in_game.tres
|
||||
./menu/lcd_screen_font.tres
|
||||
./menu/toast_exit.wav
|
||||
./menu/stack/ahses_material.tres
|
||||
./menu/stack/home.kra
|
||||
./menu/stack/fade.gd
|
||||
./menu/stack/stack.tscn
|
||||
./menu/stack/stack.gd
|
||||
./menu/stack/version.gd
|
||||
./menu/stack/art.kra
|
||||
./entity/unlock_skin_classic/icon.png
|
||||
./entity/use.gd
|
||||
./entity/chair/entity.tscn
|
||||
./entity/chair/icon.png
|
||||
./entity/chair/data.gd
|
||||
./entity/man_desert/entity.tscn
|
||||
./entity/man_desert/icon.png
|
||||
./entity/man_desert/teleprompts/need_medbay.wav
|
||||
./entity/man_desert/teleprompts/me_too.wav
|
||||
./entity/man_desert/teleprompts/get_up_alt.wav
|
||||
./entity/man_desert/teleprompts/getting_a_medpack.wav
|
||||
./entity/man_desert/teleprompts/firstaid-incoming.wav
|
||||
./entity/man_desert/teleprompts/batch_name.py
|
||||
./entity/man_desert/teleprompts/what.wav
|
||||
./entity/man_desert/teleprompts/oo.wav
|
||||
./entity/man_desert/teleprompts/yell.wav
|
||||
./entity/man_desert/teleprompts/rushing.wav
|
||||
./entity/man_desert/teleprompts/ooo.wav
|
||||
./entity/man_desert/teleprompts/coming_to_heal_ya.wav
|
||||
./entity/man_desert/teleprompts/where_is_the_medpack.wav
|
||||
./entity/man_desert/teleprompts/ah.wav
|
||||
./entity/man_desert/teleprompts/no.wav
|
||||
./entity/man_desert/teleprompts/going_to_camp_medbay.wav
|
||||
./entity/man_desert/teleprompts/aa.wav
|
||||
./entity/man_desert/teleprompts/pirate_alt.wav
|
||||
./entity/man_desert/teleprompts/take_morphine.wav
|
||||
./entity/man_desert/teleprompts/ee.wav
|
||||
./entity/man_desert/teleprompts/get_up.wav
|
||||
./entity/man_desert/teleprompts/aw.wav
|
||||
./entity/man_desert/teleprompts/easy.wav
|
||||
./entity/man_desert/teleprompts/intruder.wav
|
||||
./entity/man_desert/teleprompts/amateur.wav
|
||||
./entity/man_desert/teleprompts/hes_not_moving.wav
|
||||
./entity/man_desert/teleprompts/pirate.wav
|
||||
./entity/man_desert/teleprompts/i_dont_know.wav
|
||||
./entity/man_desert/teleprompts/index.txt
|
||||
./entity/man_desert/teleprompts/move.wav
|
||||
./entity/man_desert/teleprompts/hes_stuck.wav
|
||||
./entity/man_desert/teleprompts/how.wav
|
||||
./entity/man_desert/teleprompts/uu.wav
|
||||
./entity/man_desert/teleprompts/where_is_the_gun.wav
|
||||
./entity/man_desert/teleprompts/getting_a_gun.wav
|
||||
./entity/man_desert/data.gd
|
||||
./entity/man_desert/hand.png
|
||||
./entity/barrel_side_smoke/entity.tscn
|
||||
./entity/barrel_side_smoke/icon.png
|
||||
./entity/barrel_side_smoke/data.gd
|
||||
./entity/barrel_smoke/entity.tscn
|
||||
./entity/barrel_smoke/icon.png
|
||||
./entity/barrel_smoke/data.gd
|
||||
./entity/project_box/entity.tscn
|
||||
./entity/project_box/icon.png
|
||||
./entity/project_box/data.gd
|
||||
./entity/mutation_saw/entity.tscn
|
||||
./entity/mutation_saw/icon.png
|
||||
./entity/mutation_saw/special.gd
|
||||
./entity/mutation_saw/data.gd
|
||||
./entity/lift_entrance/entity.tscn
|
||||
./entity/lift_entrance/icon.png
|
||||
./entity/lift_entrance/special.gd
|
||||
./entity/lift_entrance/data.gd
|
||||
./entity/mutation_accuracy_boost_DELETE/entity.tscn
|
||||
./entity/mutation_accuracy_boost_DELETE/icon.png
|
||||
./entity/mutation_accuracy_boost_DELETE/special.gd
|
||||
./entity/mutation_accuracy_boost_DELETE/data.gd
|
||||
./entity/skin_ruffle/entity.tscn
|
||||
./entity/skin_ruffle/icon.png
|
||||
./entity/skin_ruffle/carried.png
|
||||
./entity/skin_ruffle/data.gd
|
||||
./entity/editor_only_icon.gd
|
||||
./entity/console_dark/entity.tscn
|
||||
./entity/console_dark/icon.png
|
||||
./entity/console_dark/data.gd
|
||||
./entity/console_dark/animation.png
|
||||
./entity/smg2/entity.tscn
|
||||
./entity/smg2/used.wav
|
||||
./entity/smg2/icon.png
|
||||
./entity/smg2/data.gd
|
||||
./entity/smg2/debug.gd
|
||||
./entity/grenade_launcher/entity.tscn
|
||||
./entity/grenade_launcher/used.wav
|
||||
./entity/grenade_launcher/icon.png
|
||||
./entity/grenade_launcher/special.gd
|
||||
./entity/grenade_launcher/data.gd
|
||||
./entity/floor_tile_full_square/entity.tscn
|
||||
./entity/floor_tile_full_square/icon.png
|
||||
./entity/floor_tile_full_square/data.gd
|
||||
./entity/grate_1/entity.tscn
|
||||
./entity/grate_1/icon.png
|
||||
./entity/grate_1/data.gd
|
||||
./entity/bed_bunk_corner/entity.tscn
|
||||
./entity/bed_bunk_corner/icon.png
|
||||
./entity/bed_bunk_corner/data.gd
|
||||
./entity/kill_streak_rail_gun_level_3/entity.tscn
|
||||
./entity/kill_streak_rail_gun_level_3/data.gd
|
||||
./entity/teleporter_random_weak/entity.tscn
|
||||
./entity/teleporter_random_weak/teleporter_model.gd
|
||||
./entity/teleporter_random_weak/used.wav
|
||||
./entity/teleporter_random_weak/icon.png
|
||||
./entity/teleporter_random_weak/special.gd
|
||||
./entity/teleporter_random_weak/ray.gd
|
||||
./entity/teleporter_random_weak/data.gd
|
||||
./entity/teleporter_random_weak/flap.png
|
||||
./entity/entities.kra
|
||||
./entity/jerry_can/entity.tscn
|
||||
./entity/jerry_can/icon.png
|
||||
./entity/jerry_can/data.gd
|
||||
./entity/kill_streak_helmet_full/entity.tscn
|
||||
./entity/kill_streak_helmet_full/data.gd
|
||||
./entity/background_derelict/background2.gd
|
||||
./entity/background_derelict/entity.tscn
|
||||
./entity/background_derelict/icon.png
|
||||
./entity/background_derelict/background/space.png
|
||||
./entity/background_derelict/background/line.png
|
||||
./entity/background_derelict/background/overlay.png
|
||||
./entity/background_derelict/background/background2.png
|
||||
./entity/background_derelict/background/background.png
|
||||
./entity/background_derelict/background/engine_glow.tscn
|
||||
./entity/background_derelict/background/lines3.png
|
||||
./entity/background_derelict/background/background.tscn
|
||||
./entity/background_derelict/background/lines.tres
|
||||
./entity/background_derelict/background/xx.gd
|
||||
./entity/background_derelict/background/background.gd
|
||||
./entity/background_derelict/background/bayer16tile2.png
|
||||
./entity/background_derelict/background/push.png
|
||||
./entity/background_derelict/background/palette_mono.png
|
||||
./entity/background_derelict/background/stars.gd
|
||||
./entity/background_derelict/background/lines2.png
|
||||
./entity/background_derelict/background/lines.shader
|
||||
./entity/background_derelict/background/ambience.gd
|
||||
./entity/background_derelict/background/space_ship_ambience.ogg
|
||||
./entity/background_derelict/background/stars.png
|
||||
./entity/background_derelict/data.gd
|
||||
./entity/smoker/entity.tscn
|
||||
./entity/smoker/right_hand.png
|
||||
./entity/smoker/eyes.png
|
||||
./entity/smoker/data.gd
|
||||
./entity/smoker/animate.gd
|
||||
./entity/smoker/left_hand.png
|
||||
./entity/EntityStatic.gd
|
||||
./entity/level_model.gd
|
||||
./entity/class_teleporter_drop_chance/entity.tscn
|
||||
./entity/class_teleporter_drop_chance/icon.png
|
||||
./entity/class_teleporter_drop_chance/special.gd
|
||||
./entity/class_teleporter_drop_chance/data.gd
|
||||
./entity/smg4/entity.tscn
|
||||
./entity/smg4/used.wav
|
||||
./entity/smg4/icon.png
|
||||
./entity/smg4/data.gd
|
||||
./entity/medpack/entity.tscn
|
||||
./entity/medpack/icon.png
|
||||
./entity/medpack/dead.png
|
||||
./entity/medpack/data.gd
|
||||
./entity/model.gd
|
||||
./entity/doom_transition/entity.tscn
|
||||
./entity/doom_transition/icon.png
|
||||
./entity/doom_transition/special.gd
|
||||
./entity/doom_transition/Screenshot from 2021-12-08 18-25-03.png
|
||||
./entity/doom_transition/data.gd
|
||||
./entity/glass_block_exploding/entity.tscn
|
||||
./entity/glass_block_exploding/icon.png
|
||||
./entity/glass_block_exploding/special.gd
|
||||
./entity/glass_block_exploding/dead.png
|
||||
./entity/glass_block_exploding/data.gd
|
||||
./entity/floor_ting/entity.tscn
|
||||
./entity/floor_ting/icon.png
|
||||
./entity/floor_ting/data.gd
|
||||
./entity/background_crashed_ship/entity.tscn
|
||||
./entity/background_crashed_ship/icon.png
|
||||
./entity/background_crashed_ship/background/background2.kra
|
||||
./entity/background_crashed_ship/background/dust_storm_negative.png
|
||||
./entity/background_crashed_ship/background/background2.png
|
||||
./entity/background_crashed_ship/background/background2 (copy 1).png
|
||||
./entity/background_crashed_ship/background/dust_bowl.ogg
|
||||
./entity/background_crashed_ship/background/background.tscn
|
||||
./entity/background_crashed_ship/background/background.kra
|
||||
./entity/background_crashed_ship/data.gd
|
||||
./entity/game_aim_hack_boss/entity.tscn
|
||||
./entity/game_aim_hack_boss/icon.png
|
||||
./entity/game_aim_hack_boss/special.gd
|
||||
./entity/game_aim_hack_boss/give_my_arm_back.wav
|
||||
./entity/game_aim_hack_boss/my_arm_came_off.wav
|
||||
./entity/game_aim_hack_boss/data.gd
|
||||
./entity/sink/entity.tscn
|
||||
./entity/sink/icon.png
|
||||
./entity/sink/data.gd
|
||||
./entity/grate_2/entity.tscn
|
||||
./entity/grate_2/icon.png
|
||||
./entity/grate_2/data.gd
|
||||
./entity/barrel_side/entity.tscn
|
||||
./entity/barrel_side/icon.png
|
||||
./entity/barrel_side/data.gd
|
||||
./entity/oxygen/entity.tscn
|
||||
./entity/oxygen/icon.png
|
||||
./entity/oxygen/shadow.png
|
||||
./entity/oxygen/data.gd
|
||||
./entity/oxygen/normal.png
|
||||
./entity/unlock_skin_robo/entity.tscn
|
||||
./entity/unlock_skin_robo/icon.png
|
||||
./entity/unlock_skin_robo/special.gd
|
||||
./entity/unlock_skin_robo/data.gd
|
||||
./entity/entity_agency_model.gd
|
||||
./entity/floor_tile_wood/entity.tscn
|
||||
./entity/floor_tile_wood/icon.png
|
||||
./entity/floor_tile_wood/data.gd
|
||||
./entity/qr_code/entity.tscn
|
||||
./entity/qr_code/icon.png
|
||||
./entity/qr_code/data.gd
|
||||
./entity/background_sun/overlay.png
|
||||
./entity/background_sun/entity.tscn
|
||||
./entity/background_sun/c.gd
|
||||
./entity/background_sun/kill.tscn
|
||||
./entity/background_sun/icon.png
|
||||
./entity/background_sun/special.gd
|
||||
./entity/background_sun/wtf.tres
|
||||
./entity/background_sun/background/background2.png
|
||||
./entity/background_sun/background/background.tscn
|
||||
./entity/background_sun/background/color2s.tres
|
||||
./entity/background_sun/background/background_glow.png
|
||||
./entity/background_sun/data.gd
|
||||
./entity/background_sun/kill.gd
|
||||
./entity/background_sun/stars.png
|
||||
./entity/background_zone_intro/overlay.png
|
||||
./entity/background_zone_intro/entity.tscn
|
||||
./entity/background_zone_intro/icon.png
|
||||
./entity/background_zone_intro/special.gd
|
||||
./entity/background_zone_intro/background/space.png
|
||||
./entity/background_zone_intro/background/line.png
|
||||
./entity/background_zone_intro/background/background2.png
|
||||
./entity/background_zone_intro/background/background.png
|
||||
./entity/background_zone_intro/background/engine_glow.tscn
|
||||
./entity/background_zone_intro/background/lines3.png
|
||||
./entity/background_zone_intro/background/background.tscn
|
||||
./entity/background_zone_intro/background/lines.tres
|
||||
./entity/background_zone_intro/background/background.gd
|
||||
./entity/background_zone_intro/background/bayer16tile2.png
|
||||
./entity/background_zone_intro/background/push.png
|
||||
./entity/background_zone_intro/background/palette_mono.png
|
||||
./entity/background_zone_intro/background/stars.gd
|
||||
./entity/background_zone_intro/background/lines2.png
|
||||
./entity/background_zone_intro/background/lines.shader
|
||||
./entity/background_zone_intro/background/ambience.gd
|
||||
./entity/background_zone_intro/background/space_ship_ambience.ogg
|
||||
./entity/background_zone_intro/background/stars.png
|
||||
./entity/background_zone_intro/background_end.png
|
||||
./entity/background_zone_intro/data.gd
|
||||
./entity/background_zone_intro/tinge.png
|
||||
./entity/closet_alt/entity.tscn
|
||||
./entity/closet_alt/icon.png
|
||||
./entity/closet_alt/data.gd
|
||||
./entity/meta_random_sound/entity.tscn
|
||||
./entity/meta_random_sound/giberish.wav
|
||||
./entity/meta_random_sound/icon.png
|
||||
./entity/meta_random_sound/special.gd
|
||||
./entity/meta_random_sound/who.wav
|
||||
./entity/meta_random_sound/data.gd
|
||||
./entity/meta_random_sound/hoola_boola.wav
|
||||
./entity/meta_random_sound/space_bandit.wav
|
||||
./entity/lines/entity.tscn
|
||||
./entity/lines/icon.png
|
||||
./entity/lines/data.gd
|
||||
./entity/teleporter_random_avoid_ray/entity.tscn
|
||||
./entity/teleporter_random_avoid_ray/used.wav
|
||||
./entity/teleporter_random_avoid_ray/icon.png
|
||||
./entity/teleporter_random_avoid_ray/ray.gd
|
||||
./entity/teleporter_random_avoid_ray/data.gd
|
||||
./entity/teleporter_random_avoid_ray/flap.png
|
||||
./entity/teleporter_random_avoid_ray/RayCast2D.gd
|
||||
./entity/teleporter_random_avoid_ray/area.gd
|
||||
./entity/teleporter_random_avoid_ray/flap.gd
|
||||
./entity/saw/blades.gd
|
||||
./entity/saw/entity.tscn
|
||||
./entity/saw/used.wav
|
||||
./entity/saw/icon.png
|
||||
./entity/saw/special.gd
|
||||
./entity/saw/carried.png
|
||||
./entity/saw/data.gd
|
||||
./entity/saw/used (copy 1).wav
|
||||
./entity/saw/saw.wav
|
||||
./entity/saw/carried_blades.png
|
||||
./entity/floor_tile_checkerdboard/damage.png
|
||||
./entity/floor_tile_checkerdboard/entity.tscn
|
||||
./entity/floor_tile_checkerdboard/icon.png
|
||||
./entity/floor_tile_checkerdboard/entity.tres
|
||||
./entity/floor_tile_checkerdboard/data.gd
|
||||
./entity/mutation_smoke_grenade_upgrade/entity.tscn
|
||||
./entity/mutation_smoke_grenade_upgrade/icon.png
|
||||
./entity/mutation_smoke_grenade_upgrade/special.gd
|
||||
./entity/mutation_smoke_grenade_upgrade/data.gd
|
||||
./entity/mutation_smoke_grenade_upgrade/mutation_model.gd
|
||||
./entity/helmet_full/entity.tscn
|
||||
./entity/helmet_full/pick_up.wav
|
||||
./entity/helmet_full/icon.png
|
||||
./entity/helmet_full/data.gd
|
||||
./entity/helmet_full/helmet-ping.wav
|
||||
./entity/barrel_explosive/entity.tscn
|
||||
./entity/barrel_explosive/icon.png
|
||||
./entity/barrel_explosive/data.gd
|
||||
./entity/bank/entity.tscn
|
||||
./entity/bank/icon.png
|
||||
./entity/bank/special.gd
|
||||
./entity/bank/data.gd
|
||||
./entity/kick/entity.tscn
|
||||
./entity/kick/swipe.png
|
||||
./entity/kick/used.wav
|
||||
./entity/kick/icon.png
|
||||
./entity/kick/AnimatedSprite.gd
|
||||
./entity/kick/data.gd
|
||||
./entity/battery/entity.tscn
|
||||
./entity/battery/icon.png
|
||||
./entity/battery/data.gd
|
||||
./entity/lift/entity.tscn
|
||||
./entity/lift/opening.wav
|
||||
./entity/lift/doors_open.png
|
||||
./entity/lift/RichTextLabel.gd
|
||||
./entity/lift/icon.png
|
||||
./entity/lift/open.wav
|
||||
./entity/lift/elevator_end.wav
|
||||
./entity/lift/lift_model.gd
|
||||
./entity/lift/label.tscn
|
||||
./entity/lift/rumble.gd
|
||||
./entity/lift/level_portal_model.gd
|
||||
./entity/lift/data.gd
|
||||
./entity/lift/doors.png
|
||||
./entity/lift/area.gd
|
||||
./entity/snes/entity.tscn
|
||||
./entity/snes/icon.png
|
||||
./entity/snes/data.gd
|
||||
./entity/passive_disarm/entity.tscn
|
||||
./entity/passive_disarm/icon.png
|
||||
./entity/passive_disarm/special.gd
|
||||
./entity/passive_disarm/data.gd
|
||||
./entity/mutation_lots_of_shot/entity.tscn
|
||||
./entity/mutation_lots_of_shot/icon.png
|
||||
./entity/mutation_lots_of_shot/special.gd
|
||||
./entity/mutation_lots_of_shot/data.gd
|
||||
./entity/pallet2/entity.tscn
|
||||
./entity/pallet2/icon.png
|
||||
./entity/pallet2/data.gd
|
||||
./entity/kill_streak_sword/entity.tscn
|
||||
./entity/kill_streak_sword/data.gd
|
||||
./entity/rain/entity.tscn
|
||||
./entity/rain/icon.png
|
||||
./entity/rain/special.gd
|
||||
./entity/rain/rain.png
|
||||
./entity/rain/rain.tscn
|
||||
./entity/rain/data.gd
|
||||
./entity/rain/rain.gd
|
||||
./entity/white_line/entity.tscn
|
||||
./entity/white_line/icon.png
|
||||
./entity/white_line/data.gd
|
||||
./entity/game_break_sword/entity.tscn
|
||||
./entity/game_break_sword/icon.png
|
||||
./entity/game_break_sword/special.gd
|
||||
./entity/game_break_sword/data.gd
|
||||
./entity/background_zone1/overlay.png
|
||||
./entity/background_zone1/entity.tscn
|
||||
./entity/background_zone1/icon.png
|
||||
./entity/background_zone1/special.gd
|
||||
./entity/background_zone1/background/space.png
|
||||
./entity/background_zone1/background/line.png
|
||||
./entity/background_zone1/background/background2.png
|
||||
./entity/background_zone1/background/background.png
|
||||
./entity/background_zone1/background/engine_glow.tscn
|
||||
./entity/background_zone1/background/lines3.png
|
||||
./entity/background_zone1/background/background.tscn
|
||||
./entity/background_zone1/background/lines.tres
|
||||
./entity/background_zone1/background/background.gd
|
||||
./entity/background_zone1/background/bayer16tile2.png
|
||||
./entity/background_zone1/background/push.png
|
||||
./entity/background_zone1/background/palette_mono.png
|
||||
./entity/background_zone1/background/stars.gd
|
||||
./entity/background_zone1/background/lines2.png
|
||||
./entity/background_zone1/background/lines.shader
|
||||
./entity/background_zone1/background/ambience.gd
|
||||
./entity/background_zone1/background/space_ship_ambience.ogg
|
||||
./entity/background_zone1/background/stars.png
|
||||
./entity/background_zone1/data.gd
|
||||
./entity/background_zone1/tinge.png
|
||||
./entity/mutation_throw_trap_DELETE/entity.tscn
|
||||
./entity/mutation_throw_trap_DELETE/icon.png
|
||||
./entity/mutation_throw_trap_DELETE/special.gd
|
||||
./entity/mutation_throw_trap_DELETE/data.gd
|
||||
./entity/agency.gd
|
||||
./entity/skin_cheese/entity.tscn
|
||||
./entity/skin_cheese/icon.png
|
||||
./entity/skin_cheese/carried.png
|
||||
./entity/skin_cheese/data.gd
|
||||
./entity/toilet/entity.tscn
|
||||
./entity/toilet/icon.png
|
||||
./entity/toilet/special.gd
|
||||
./entity/toilet/water.png
|
||||
./entity/toilet/drink.wav
|
||||
./entity/toilet/data.gd
|
||||
./entity/smg3/entity.tscn
|
||||
./entity/smg3/used.wav
|
||||
./entity/smg3/icon.png
|
||||
./entity/smg3/dead.png
|
||||
./entity/smg3/data.gd
|
||||
./entity/smg3/debug.gd
|
||||
./entity/teleporter_super/entity.tscn
|
||||
./entity/teleporter_super/icon.png
|
||||
./entity/teleporter_super/data.gd
|
||||
./entity/background_zone_end/overlay.png
|
||||
./entity/background_zone_end/entity.tscn
|
||||
./entity/background_zone_end/icon.png
|
||||
./entity/background_zone_end/special.gd
|
||||
./entity/background_zone_end/stars2.png
|
||||
./entity/background_zone_end/background_end.png
|
||||
./entity/background_zone_end/data.gd
|
||||
./entity/background_zone_end/tinge.png
|
||||
./entity/kill_streak_barricade/entity.tscn
|
||||
./entity/kill_streak_barricade/data.gd
|
||||
./entity/game_zone_4_boss_1/entity.tscn
|
||||
./entity/game_zone_4_boss_1/icon.png
|
||||
./entity/game_zone_4_boss_1/special.gd
|
||||
./entity/game_zone_4_boss_1/data.gd
|
||||
./entity/game_zone_4_boss_1/kill_me_and_explode_ship.wav
|
||||
./entity/mutation_remove_melee/entity.tscn
|
||||
./entity/mutation_remove_melee/icon.png
|
||||
./entity/mutation_remove_melee/special.gd
|
||||
./entity/mutation_remove_melee/data.gd
|
||||
./entity/he_grenade_level_2/entity.tscn
|
||||
./entity/he_grenade_level_2/icon.png
|
||||
./entity/he_grenade_level_2/data.gd
|
||||
./entity/background_zone_2/entity.tscn
|
||||
./entity/background_zone_2/icon.png
|
||||
./entity/background_zone_2/background/background2.kra
|
||||
./entity/background_zone_2/background/grad.png
|
||||
./entity/background_zone_2/background/background2.png
|
||||
./entity/background_zone_2/background/background.png
|
||||
./entity/background_zone_2/background/background2 (copy 1).png
|
||||
./entity/background_zone_2/background/backgrounds.gd
|
||||
./entity/background_zone_2/background/wall_overlay.png
|
||||
./entity/background_zone_2/background/background.tscn
|
||||
./entity/background_zone_2/background/Screenshot from 2022-07-07 10-58-48.png
|
||||
./entity/background_zone_2/background/background.gd
|
||||
./entity/background_zone_2/background/shadow.png
|
||||
./entity/background_zone_2/background/engine smoke.png
|
||||
./entity/background_zone_2/background/background.kra
|
||||
./entity/background_zone_2/background/sea.ogg
|
||||
./entity/background_zone_2/background/background2blur.png
|
||||
./entity/background_zone_2/background/test.gd
|
||||
./entity/background_zone_2/background/grad3.png
|
||||
./entity/background_zone_2/background/lines2.png
|
||||
./entity/background_zone_2/background/smoke.tscn
|
||||
./entity/background_zone_2/background/left_water.tscn
|
||||
./entity/background_zone_2/background/grad2.png
|
||||
./entity/background_zone_2/background/para.png
|
||||
./entity/background_zone_2/data.gd
|
||||
./entity/pipe_corner/entity.tscn
|
||||
./entity/pipe_corner/icon.png
|
||||
./entity/pipe_corner/data.gd
|
||||
./entity/floor_tile_metal_cow_trap/entity.tscn
|
||||
./entity/floor_tile_metal_cow_trap/icon.png
|
||||
./entity/floor_tile_metal_cow_trap/data.gd
|
||||
./entity/skin_naked/entity.tscn
|
||||
./entity/skin_naked/icon.png
|
||||
./entity/skin_naked/carried.png
|
||||
./entity/skin_naked/data.gd
|
||||
./entity/valve/entity.tscn
|
||||
./entity/valve/icon.png
|
||||
./entity/valve/.icon.png-autosave.kra
|
||||
./entity/valve/data.gd
|
||||
./entity/bed/entity.tscn
|
||||
./entity/bed/icon.png
|
||||
./entity/bed/data.gd
|
||||
./entity/game_invisible_guy/entity.tscn
|
||||
./entity/game_invisible_guy/icon.png
|
||||
./entity/game_invisible_guy/special.gd
|
||||
./entity/game_invisible_guy/data.gd
|
||||
./entity/smg/entity.tscn
|
||||
./entity/smg/used.wav
|
||||
./entity/smg/icon.png
|
||||
./entity/smg/data.gd
|
||||
./entity/skin_robo/entity.tscn
|
||||
./entity/skin_robo/icon.png
|
||||
./entity/skin_robo/carried.png
|
||||
./entity/skin_robo/data.gd
|
||||
./entity/bandana/entity.tscn
|
||||
./entity/bandana/bob.gd
|
||||
./entity/bandana/icon.png
|
||||
./entity/bandana/special.gd
|
||||
./entity/bandana/carried.png
|
||||
./entity/bandana/data.gd
|
||||
./entity/bandana/pixel.png
|
||||
./entity/floor_plug/entity.tscn
|
||||
./entity/floor_plug/icon.png
|
||||
./entity/floor_plug/data.gd
|
||||
./entity/bench/entity.tscn
|
||||
./entity/bench/icon.png
|
||||
./entity/bench/data.gd
|
||||
./entity/meta_strip_items/entity.tscn
|
||||
./entity/meta_strip_items/special.gd
|
||||
./entity/meta_strip_items/meta_strip_items_model.gd
|
||||
./entity/meta_strip_items/data.gd
|
||||
./entity/crate_teleporter/entity.tscn
|
||||
./entity/crate_teleporter/icon.png
|
||||
./entity/crate_teleporter/data.gd
|
||||
./entity/crate_teleporter/satellite.kra
|
||||
./entity/crate_garbage/entity.tscn
|
||||
./entity/crate_garbage/icon.png
|
||||
./entity/crate_garbage/data.gd
|
||||
./entity/crate_garbage/gibbed.png
|
||||
./entity/meta_stats/entity.tscn
|
||||
./entity/meta_stats/letters.tres
|
||||
./entity/meta_stats/icon.png
|
||||
./entity/meta_stats/special.gd
|
||||
./entity/meta_stats/data.gd
|
||||
./entity/meta_stats/meta_stats_model.gd
|
||||
./entity/rail_gun/entity.tscn
|
||||
./entity/rail_gun/used.wav
|
||||
./entity/rail_gun/icon.png
|
||||
./entity/rail_gun/special.gd
|
||||
./entity/rail_gun/carried.png
|
||||
./entity/rail_gun/data.gd
|
||||
./entity/drop_ship_door/entity.tscn
|
||||
./entity/drop_ship_door/icon.png
|
||||
./entity/drop_ship_door/data.gd
|
||||
./entity/floor_lines/entity.tscn
|
||||
./entity/floor_lines/icon.png
|
||||
./entity/floor_lines/data.gd
|
||||
./entity/game_trap/entity.tscn
|
||||
./entity/game_trap/you_blew_up_my_force_field.wav
|
||||
./entity/game_trap/droped_my_grenade_2.wav
|
||||
./entity/game_trap/icon.png
|
||||
./entity/game_trap/special.gd
|
||||
./entity/game_trap/droped_my_grenade_0.wav
|
||||
./entity/game_trap/shock.wav
|
||||
./entity/game_trap/uh_my_helmet.wav
|
||||
./entity/game_trap/ha_missed_me.wav
|
||||
./entity/game_trap/data.gd
|
||||
./entity/game_trap/try_beat_this_force_field.wav
|
||||
./entity/game_trap/droped_my_grenade_1.wav
|
||||
./entity/blood_sword/entity.tscn
|
||||
./entity/blood_sword/pick_up.wav
|
||||
./entity/blood_sword/used.wav
|
||||
./entity/blood_sword/sam2.png
|
||||
./entity/blood_sword/icon.png
|
||||
./entity/blood_sword/special.gd
|
||||
./entity/blood_sword/hit_bar.gd
|
||||
./entity/blood_sword/data.gd
|
||||
./entity/blood_sword/sam.png
|
||||
./entity/blood_sword/dead.wav
|
||||
./entity/blood_sword/animation.png
|
||||
./entity/auto_cables_thick/entity.tscn
|
||||
./entity/auto_cables_thick/data.gd
|
||||
./entity/auto_cables_thick/wires2.png
|
||||
./entity/shield/entity.tscn
|
||||
./entity/shield/pick_up.wav
|
||||
./entity/shield/icon.png
|
||||
./entity/shield/carried.png
|
||||
./entity/shield/data.gd
|
||||
./entity/shield/helmet-ping.wav
|
||||
./entity/game_teleport_in/entity.tscn
|
||||
./entity/game_teleport_in/icon.png
|
||||
./entity/game_teleport_in/special.gd
|
||||
./entity/game_teleport_in/data.gd
|
||||
./entity/shotgun_super/entity.tscn
|
||||
./entity/shotgun_super/icon.png
|
||||
./entity/shotgun_super/data.gd
|
||||
./entity/bottle/entity.tscn
|
||||
./entity/bottle/icon.png
|
||||
./entity/bottle/data.gd
|
||||
./entity/bottle/normal.png
|
||||
./entity/bottle/icon_shadow.png
|
||||
./entity/kill_streak_p90/entity.tscn
|
||||
./entity/kill_streak_p90/data.gd
|
||||
./entity/drain/entity.tscn
|
||||
./entity/drain/icon.png
|
||||
./entity/drain/data.gd
|
||||
./entity/auto_wires_three/entity.tscn
|
||||
./entity/auto_wires_three/data.gd
|
||||
./entity/light/entity.tscn
|
||||
./entity/light/icon.png
|
||||
./entity/light/special.gd
|
||||
./entity/light/light.wav
|
||||
./entity/light/data.gd
|
||||
./entity/debris/entity.tscn
|
||||
./entity/debris/icon.png
|
||||
./entity/debris/data.gd
|
||||
./entity/debris/gibbed.png
|
||||
./entity/mutation_rail_gun_upgrade/entity.tscn
|
||||
./entity/mutation_rail_gun_upgrade/icon.png
|
||||
./entity/mutation_rail_gun_upgrade/special.gd
|
||||
./entity/mutation_rail_gun_upgrade/data.gd
|
||||
./entity/mutation_rail_gun_upgrade/mutation_model.gd
|
||||
./entity/auto_cables/entity.tscn
|
||||
./entity/auto_cables/data.gd
|
||||
./entity/auto_cables/wires2.png
|
||||
./entity/stealth_camo/entity.tscn
|
||||
./entity/stealth_camo/special.gd
|
||||
./entity/stealth_camo/data.gd
|
||||
./entity/colt_45/entity.tscn
|
||||
./entity/colt_45/used.wav
|
||||
./entity/colt_45/icon.png
|
||||
./entity/colt_45/dead.png
|
||||
./entity/colt_45/data.gd
|
||||
./entity/quantum_suicide_drive/entity.tscn
|
||||
./entity/quantum_suicide_drive/heart.ogg
|
||||
./entity/quantum_suicide_drive/icon.png
|
||||
./entity/quantum_suicide_drive/special.gd
|
||||
./entity/quantum_suicide_drive/qsd_model.gd
|
||||
./entity/quantum_suicide_drive/multi.gd
|
||||
./entity/quantum_suicide_drive/multi.tscn
|
||||
./entity/quantum_suicide_drive/CenterContainer.gd
|
||||
./entity/quantum_suicide_drive/carried.png
|
||||
./entity/quantum_suicide_drive/data.gd
|
||||
./entity/helmet/entity.tscn
|
||||
./entity/helmet/pick_up.wav
|
||||
./entity/helmet/icon.png
|
||||
./entity/helmet/special.gd
|
||||
./entity/helmet/die.wav
|
||||
./entity/helmet/carried.png
|
||||
./entity/helmet/data.gd
|
||||
./entity/helmet/helmet-ping.wav
|
||||
./entity/ammo_box/entity.tscn
|
||||
./entity/ammo_box/icon.png
|
||||
./entity/ammo_box/data.gd
|
||||
./entity/rail_gun_level_2/entity.tscn
|
||||
./entity/rail_gun_level_2/icon.png
|
||||
./entity/rail_gun_level_2/data.gd
|
||||
./entity/glass_block_backup/entity.tscn
|
||||
./entity/glass_block_backup/icon.png
|
||||
./entity/glass_block_backup/data.gd
|
||||
./entity/closet/entity.tscn
|
||||
./entity/closet/icon.png
|
||||
./entity/closet/data.gd
|
||||
./entity/little_boxes/entity.tscn
|
||||
./entity/little_boxes/icon.png
|
||||
./entity/little_boxes/data.gd
|
||||
./entity/meta_health_bar/entity.tscn
|
||||
./entity/meta_health_bar/health_bar_model.gd
|
||||
./entity/meta_health_bar/icon.png
|
||||
./entity/meta_health_bar/special.gd
|
||||
./entity/meta_health_bar/invunerable.png
|
||||
./entity/meta_health_bar/data.gd
|
||||
./entity/night_stand/entity.tscn
|
||||
./entity/night_stand/icon_normal.png
|
||||
./entity/night_stand/icon.png
|
||||
./entity/night_stand/shadow.png
|
||||
./entity/night_stand/data.gd
|
||||
./entity/fan/entity.tscn
|
||||
./entity/fan/flap2.png
|
||||
./entity/fan/flaps.gd
|
||||
./entity/fan/icon.png
|
||||
./entity/fan/data.gd
|
||||
./entity/fan/flap.png
|
||||
./entity/fan/icon_shadow.png
|
||||
./entity/fan/animation.png
|
||||
./entity/fan/gibbed.png
|
||||
./entity/game_tutorial_end/entity.tscn
|
||||
./entity/game_tutorial_end/icon.png
|
||||
./entity/game_tutorial_end/special.gd
|
||||
./entity/game_tutorial_end/data.gd
|
||||
./entity/mutation_disarmament/entity.tscn
|
||||
./entity/mutation_disarmament/icon.png
|
||||
./entity/mutation_disarmament/special.gd
|
||||
./entity/mutation_disarmament/data.gd
|
||||
./entity/air_lock/icon_open.png
|
||||
./entity/air_lock/entity.tscn
|
||||
./entity/air_lock/door_close.wav
|
||||
./entity/air_lock/icon.png
|
||||
./entity/air_lock/special.gd
|
||||
./entity/air_lock/air_lock_model.gd
|
||||
./entity/air_lock/data.gd
|
||||
./entity/scorpion/entity.tscn
|
||||
./entity/scorpion/used.wav
|
||||
./entity/scorpion/laser.gd
|
||||
./entity/scorpion/icon.png
|
||||
./entity/scorpion/data.gd
|
||||
./entity/kill_streak_aim_hack/entity.tscn
|
||||
./entity/kill_streak_aim_hack/data.gd
|
||||
./entity/dungeon_proc_debug/entity.tscn
|
||||
./entity/dungeon_proc_debug/icon.png
|
||||
./entity/dungeon_proc_debug/data.gd
|
||||
./entity/dungeon_proc_debug/debug.gd
|
||||
./entity/dungeon_proc_debug/debug.tscn
|
||||
./entity/tarp/entity.tscn
|
||||
./entity/tarp/icon.png
|
||||
./entity/tarp/data.gd
|
||||
./entity/hit_indicator/entity.tscn
|
||||
./entity/hit_indicator/data.gd
|
||||
./entity/console_corner/entity.tscn
|
||||
./entity/console_corner/animation2.tscn
|
||||
./entity/console_corner/icon.png
|
||||
./entity/console_corner/data.gd
|
||||
./entity/console_corner/animation.tscn
|
||||
./entity/icon.png
|
||||
./entity/couch_corner/entity.tscn
|
||||
./entity/couch_corner/icon.png
|
||||
./entity/couch_corner/data.gd
|
||||
./entity/m4/entity.tscn
|
||||
./entity/m4/used.wav
|
||||
./entity/m4/icon.png
|
||||
./entity/m4/data.gd
|
||||
./entity/game_hud/entity.tscn
|
||||
./entity/game_hud/icon.png
|
||||
./entity/game_hud/data.gd
|
||||
./entity/game_hud/inventory_game.tscn
|
||||
./entity/prototypes.gd
|
||||
./entity/agent_chicken/emotes.png
|
||||
./entity/agent_chicken/entity.tscn
|
||||
./entity/agent_chicken/sound_board.gd
|
||||
./entity/agent_chicken/bones.tscn
|
||||
./entity/agent_chicken/bones.gd
|
||||
./entity/agent_chicken/barks.gd
|
||||
./entity/agent_chicken/emote.gd
|
||||
./entity/agent_chicken/icon.png
|
||||
./entity/agent_chicken/special.gd
|
||||
./entity/agent_chicken/bark.gd
|
||||
./entity/agent_chicken/deaad.png
|
||||
./entity/agent_chicken/icon.gd
|
||||
./entity/agent_chicken/data.gd
|
||||
./entity/agent_chicken/animation.tscn
|
||||
./entity/agent_chicken/emote.tscn
|
||||
./entity/agent_chicken/hand.png
|
||||
./entity/velocity/entity.tscn
|
||||
./entity/velocity/icon.png
|
||||
./entity/velocity/special.gd
|
||||
./entity/velocity/data.gd
|
||||
./entity/aircon/entity.tscn
|
||||
./entity/aircon/grate.png
|
||||
./entity/aircon/icon.png
|
||||
./entity/aircon/data.gd
|
||||
./entity/aircon/animation.png
|
||||
./entity/floor_tile_bricks/entity.tscn
|
||||
./entity/floor_tile_bricks/icon.png
|
||||
./entity/floor_tile_bricks/data.gd
|
||||
./entity/pallet/entity.tscn
|
||||
./entity/pallet/icon.png
|
||||
./entity/pallet/data.gd
|
||||
./entity/barricade_deployed/debug.png
|
||||
./entity/barricade_deployed/field.tscn
|
||||
./entity/barricade_deployed/entity.tscn
|
||||
./entity/barricade_deployed/ambience.ogg
|
||||
./entity/barricade_deployed/icon.png
|
||||
./entity/barricade_deployed/field.gd
|
||||
./entity/barricade_deployed/field_material.tres
|
||||
./entity/barricade_deployed/debug2.png
|
||||
./entity/barricade_deployed/data.gd
|
||||
./entity/barricade_deployed/field_material_invert.tres
|
||||
./entity/barricade_deployed/field_material.gd
|
||||
./entity/barricade_deployed/gibbed.png
|
||||
./entity/helmet_nv/entity.tscn
|
||||
./entity/helmet_nv/pick_up.wav
|
||||
./entity/helmet_nv/icon.png
|
||||
./entity/helmet_nv/special.gd
|
||||
./entity/helmet_nv/carried.png
|
||||
./entity/helmet_nv/eyes.png
|
||||
./entity/helmet_nv/data.gd
|
||||
./entity/helmet_nv/helmet-ping.wav
|
||||
./entity/helmet_nv/eyes.gd
|
||||
./entity/mutation_sword/entity.tscn
|
||||
./entity/mutation_sword/icon.png
|
||||
./entity/mutation_sword/special.gd
|
||||
./entity/mutation_sword/data.gd
|
||||
./entity/field_full_super/entity.tscn
|
||||
./entity/field_full_super/icon.png
|
||||
./entity/field_full_super/special.gd
|
||||
./entity/field_full_super/carried.png
|
||||
./entity/field_full_super/data.gd
|
||||
./entity/entity_man.gd
|
||||
./entity/couch/entity.tscn
|
||||
./entity/couch/icon.png
|
||||
./entity/couch/data.gd
|
||||
./entity/teleporter_lil_hunter/entity.tscn
|
||||
./entity/teleporter_lil_hunter/icon.png
|
||||
./entity/teleporter_lil_hunter/tubes.png
|
||||
./entity/teleporter_lil_hunter/osc_shader.tres
|
||||
./entity/teleporter_lil_hunter/eyes.png
|
||||
./entity/teleporter_lil_hunter/data.gd
|
||||
./entity/teleporter_lil_hunter/osc.tres
|
||||
./entity/game_tutorial_melee_zone/entity.tscn
|
||||
./entity/game_tutorial_melee_zone/icon.png
|
||||
./entity/game_tutorial_melee_zone/special.gd
|
||||
./entity/game_tutorial_melee_zone/data.gd
|
||||
./entity/kill_streak_glock/entity.tscn
|
||||
./entity/kill_streak_glock/data.gd
|
||||
./entity/skin_mime/entity.tscn
|
||||
./entity/skin_mime/icon.png
|
||||
./entity/skin_mime/special.gd
|
||||
./entity/skin_mime/carried.png
|
||||
./entity/skin_mime/data.gd
|
||||
./entity/medpack_hard/entity.tscn
|
||||
./entity/medpack_hard/icon.png
|
||||
./entity/medpack_hard/data.gd
|
||||
./entity/teleporter_overload/entity.tscn
|
||||
./entity/teleporter_overload/icon.png
|
||||
./entity/teleporter_overload/special.gd
|
||||
./entity/teleporter_overload/carried.png
|
||||
./entity/teleporter_overload/data.gd
|
||||
./entity/background_freighter/overlay.png
|
||||
./entity/background_freighter/entity.tscn
|
||||
./entity/background_freighter/icon.png
|
||||
./entity/background_freighter/Master.ogg
|
||||
./entity/background_freighter/background/space.png
|
||||
./entity/background_freighter/background/line.png
|
||||
./entity/background_freighter/background/background2.gd
|
||||
./entity/background_freighter/background/good create.png
|
||||
./entity/background_freighter/background/backgip.png
|
||||
./entity/background_freighter/background/background2.png
|
||||
./entity/background_freighter/background/background.png
|
||||
./entity/background_freighter/background/engine_glow.tscn
|
||||
./entity/background_freighter/background/gra2d.png
|
||||
./entity/background_freighter/background/lines3.png
|
||||
./entity/background_freighter/background/background.tscn
|
||||
./entity/background_freighter/background/lines.tres
|
||||
./entity/background_freighter/background/background.gd
|
||||
./entity/background_freighter/background/bayer16tile2.png
|
||||
./entity/background_freighter/background/goodcrate.png
|
||||
./entity/background_freighter/background/push.png
|
||||
./entity/background_freighter/background/background_floor.png
|
||||
./entity/background_freighter/background/palette_mono.png
|
||||
./entity/background_freighter/background/stars.gd
|
||||
./entity/background_freighter/background/lines2.png
|
||||
./entity/background_freighter/background/lines.shader
|
||||
./entity/background_freighter/background/ambience.gd
|
||||
./entity/background_freighter/background/bacsdas.png
|
||||
./entity/background_freighter/background/space_ship_ambience.ogg
|
||||
./entity/background_freighter/background/stars.png
|
||||
./entity/background_freighter/data.gd
|
||||
./entity/auto_wires/entity.tscn
|
||||
./entity/auto_wires/data.gd
|
||||
./entity/kill_streak/entity.tscn
|
||||
./entity/kill_streak/kill_streak_toast.tscn
|
||||
./entity/kill_streak/icon.png
|
@ -82,6 +82,7 @@
|
||||
#include "tests/core/object/test_object.h"
|
||||
#include "tests/core/object/test_undo_redo.h"
|
||||
#include "tests/core/os/test_os.h"
|
||||
#include "tests/core/string/test_fuzzy_search.h"
|
||||
#include "tests/core/string/test_node_path.h"
|
||||
#include "tests/core/string/test_string.h"
|
||||
#include "tests/core/string/test_translation.h"
|
||||
|
Loading…
Reference in New Issue
Block a user