Follow the GDScript style guide in every script
- Types on every parameter, return value and variable, and snake_case names (the player name limit is the constant MAX_NAME_LENGTH). - Spelling fixes: *_triggered, character_selected, separator, caught_errors. The server's catched_errors key stays as the server sends it. - Signals are connected with signal.connect() instead of by name. - Code is ordered signals, constants, exported vars, vars, @onready vars, functions, with @onready on one line and two blank lines between functions. - Every script starts with a ## description. Template comments, old LOC notes and commented-out code are gone, apart from the local play code. - Lines are at most 100 characters, apart from the local play path. - The screenshot tour turns off the blinking text cursor so pictures match.
This commit is contained in:
1 parent
674604425f
commit
a0428e3475
45 files changed
+580
-589
No files matched your search
+48
-38
@@ -1,65 +1,65 @@
|
||||
class_name SearchPopup
|
||||
extends BasePopup
|
||||
|
||||
@onready
|
||||
var search_list: VBoxContainer = %SearchResults
|
||||
|
||||
@onready
|
||||
var search_panel: Panel = %SearchPanel
|
||||
|
||||
@onready
|
||||
var clear_button: Button = $ClearButton
|
||||
|
||||
@onready
|
||||
var search_bar: TextEdit = $Searchbar
|
||||
## Searches every soundtrack on the server as you type. Matches ignore case, spaces and
|
||||
## special characters, and also find the letters in order with others in between.
|
||||
|
||||
var soundtracks: Array = []
|
||||
|
||||
# Precomputed soundtrack data: array of dicts with original, lowercase, and cleaned fields
|
||||
## Each soundtrack as a Dictionary with its original, lowercase and cleaned name.
|
||||
var precomputed_soundtracks: Array = []
|
||||
|
||||
# Regex cache: maps cleaned search terms to compiled RegEx objects
|
||||
## Compiled patterns for the letters-in-order search, by cleaned search term.
|
||||
var regex_cache: Dictionary = {}
|
||||
|
||||
var label_pool: Array[Label] = []
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
@onready var search_list: VBoxContainer = %SearchResults
|
||||
@onready var search_panel: Panel = %SearchPanel
|
||||
@onready var clear_button: Button = $ClearButton
|
||||
@onready var search_bar: TextEdit = $Searchbar
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
clear_button.pressed.connect(clear)
|
||||
search_bar.grab_focus()
|
||||
search_bar.text_changed.connect(search)
|
||||
visibility_changed.connect(focus)
|
||||
|
||||
|
||||
func focus() -> void:
|
||||
if self.visible == true:
|
||||
if visible:
|
||||
search_bar.grab_focus()
|
||||
clear()
|
||||
|
||||
|
||||
func close() -> void:
|
||||
clear()
|
||||
super()
|
||||
|
||||
|
||||
func search() -> void:
|
||||
GameLog.debug(search_bar.text)
|
||||
var search_text: String = search_bar.text
|
||||
|
||||
|
||||
if search_text == "":
|
||||
show_all_soundtracks()
|
||||
return
|
||||
|
||||
|
||||
perform_search(search_text)
|
||||
|
||||
|
||||
func show_all_soundtracks() -> void:
|
||||
# Hide all visible labels and add to pool
|
||||
for child: Label in search_list.get_children():
|
||||
if child.visible:
|
||||
child.visible = false
|
||||
label_pool.append(child)
|
||||
|
||||
|
||||
# Show all soundtracks in order
|
||||
for soundtrack: String in soundtracks:
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
|
||||
|
||||
func perform_search(search_text: String) -> void:
|
||||
# Hide all visible labels and add to pool
|
||||
for child: Label in search_list.get_children():
|
||||
@@ -70,6 +70,7 @@ func perform_search(search_text: String) -> void:
|
||||
for soundtrack: String in find_matches(search_text):
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
|
||||
|
||||
## Returns the soundtracks matching [param search_text], best matches first: plain
|
||||
## case-insensitive matches, then matches ignoring spaces and special characters,
|
||||
## then matches with the letters in order but not next to each other.
|
||||
@@ -81,25 +82,25 @@ func find_matches(search_text: String) -> Array[String]:
|
||||
var matches: Array[String] = []
|
||||
|
||||
# Pass 1: Exact case-insensitive contains match on lowercase versions
|
||||
for i in range(precomputed_soundtracks.size()):
|
||||
var st_data = precomputed_soundtracks[i]
|
||||
for i: int in range(precomputed_soundtracks.size()):
|
||||
var st_data: Dictionary = precomputed_soundtracks[i]
|
||||
if st_data["lowercase"].contains(search_text_lower):
|
||||
if not st_data["original"] in seen:
|
||||
seen[st_data["original"]] = true
|
||||
matches.append(st_data["original"])
|
||||
|
||||
# Pass 2: Contains match on cleaned terms
|
||||
for i in range(precomputed_soundtracks.size()):
|
||||
var st_data = precomputed_soundtracks[i]
|
||||
for i: int in range(precomputed_soundtracks.size()):
|
||||
var st_data: Dictionary = precomputed_soundtracks[i]
|
||||
if st_data["cleaned"].contains(clean_search_text):
|
||||
if not st_data["original"] in seen:
|
||||
seen[st_data["original"]] = true
|
||||
matches.append(st_data["original"])
|
||||
|
||||
# Pass 3: Regex match on cleaned terms
|
||||
var regex = compile_regex_cached(clean_search_text)
|
||||
for i in range(precomputed_soundtracks.size()):
|
||||
var st_data = precomputed_soundtracks[i]
|
||||
var regex: RegEx = compile_regex_cached(clean_search_text)
|
||||
for i: int in range(precomputed_soundtracks.size()):
|
||||
var st_data: Dictionary = precomputed_soundtracks[i]
|
||||
if regex.search(st_data["cleaned"]):
|
||||
if not st_data["original"] in seen:
|
||||
seen[st_data["original"]] = true
|
||||
@@ -107,30 +108,35 @@ func find_matches(search_text: String) -> Array[String]:
|
||||
|
||||
return matches
|
||||
|
||||
# Cleans a term by removing spaces and replacing special characters.
|
||||
# Matches MusicServer's clean_term() function.
|
||||
func clean_term(term: String) -> String:
|
||||
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and").replace("'n", "and").to_lower()
|
||||
|
||||
# Compiles a regex pattern with caching to avoid recompiling the same pattern multiple times.
|
||||
## Cleans a term by removing spaces and replacing special characters.
|
||||
## Matches MusicServer's clean_term() function.
|
||||
func clean_term(term: String) -> String:
|
||||
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and") \
|
||||
.replace("'n", "and").to_lower()
|
||||
|
||||
|
||||
## Compiles the letters-in-order pattern for [param search_term], once per term.
|
||||
func compile_regex_cached(search_term: String) -> RegEx:
|
||||
if not search_term in regex_cache:
|
||||
var regex = RegEx.new()
|
||||
var regText: String = ".*"
|
||||
var regex: RegEx = RegEx.new()
|
||||
var regex_text: String = ".*"
|
||||
for letter: String in search_term:
|
||||
# Escape regex metacharacters so searches like "Q*bert" or "(Remix)" work
|
||||
if "\\^$.|?*+()[]{}".contains(letter):
|
||||
regText += "\\"
|
||||
regText += letter + ".*"
|
||||
regex.compile(regText)
|
||||
regex_text += "\\"
|
||||
regex_text += letter + ".*"
|
||||
regex.compile(regex_text)
|
||||
regex_cache[search_term] = regex
|
||||
return regex_cache[search_term]
|
||||
|
||||
|
||||
func add_soundtrack(soundtrack: String) -> void:
|
||||
# Backward compatibility - delegates to reuse version
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
|
||||
# Optimized version that reuses label nodes from pool instead of creating new ones
|
||||
|
||||
## Shows [param soundtrack], reusing a hidden label from the pool when there is one.
|
||||
func add_soundtrack_reuse(soundtrack: String) -> void:
|
||||
var label: Label
|
||||
if label_pool.size() > 0:
|
||||
@@ -146,6 +152,7 @@ func add_soundtrack_reuse(soundtrack: String) -> void:
|
||||
search_list.add_child(label)
|
||||
label.visible = true
|
||||
|
||||
|
||||
func check_if_soundtrack_exists(soundtrack: String) -> bool:
|
||||
var soundtrack_exists: bool = false
|
||||
for child: Label in search_list.get_children():
|
||||
@@ -153,9 +160,10 @@ func check_if_soundtrack_exists(soundtrack: String) -> bool:
|
||||
soundtrack_exists = true
|
||||
return soundtrack_exists
|
||||
|
||||
|
||||
func get_list_of_soundtracks() -> void:
|
||||
GameLog.debug("get_list_of_soundtracks")
|
||||
var handle_soundtracks: Callable = func handle_soundtracks(array) -> void:
|
||||
var handle_soundtracks: Callable = func handle_soundtracks(array: Variant) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
# Reuse existing labels: hide all and add to pool
|
||||
for child: Label in search_list.get_children():
|
||||
@@ -168,6 +176,7 @@ func get_list_of_soundtracks() -> void:
|
||||
GameLog.warning("Unexpected data")
|
||||
MusicServerClient.fetch_all_soundtracks_ordered(handle_soundtracks)
|
||||
|
||||
|
||||
## Replaces the searchable soundtracks and precomputes their search forms.
|
||||
func set_soundtracks(list: Array) -> void:
|
||||
soundtracks = []
|
||||
@@ -180,6 +189,7 @@ func set_soundtracks(list: Array) -> void:
|
||||
"cleaned": clean_term(soundtrack)
|
||||
})
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
search_bar.text = ""
|
||||
show_all_soundtracks()
|
||||
|
||||
Reference in new issue
Block a user