Rework the Search popup and highlight what matched
- The search field is a single-line field, so Enter no longer adds a
line break that made every search come up empty.
- The field, button and list use real font sizes from the theme instead
of being drawn at half size and scaled up, so the text is sharp, and
everything fits inside the panel.
- Results have alternating row backgrounds like the other lists, a count
("3 of 1045 soundtracks") and a message when nothing matches.
- The letters that matched are highlighted.
- Searches shorter than Settings.LETTERS_FOR_LOOSE_SEARCH (3) only find
names that contain the text, instead of names with the letters far
apart, which matched most of the library. The Statistics tab uses the
same search.
This commit is contained in:
1 parent
13fdb3f68c
commit
845d08c260
12 files changed
+2063
-808
No files matched your search
@@ -103,12 +103,12 @@ func test_button_works_again_after_popup_closed() -> void:
|
||||
func test_closing_search_clears_the_search() -> void:
|
||||
var search: BasePopup = _popup("SearchPopup")
|
||||
search.open()
|
||||
var search_bar: TextEdit = search.get_node("Searchbar")
|
||||
search_bar.text = "mario"
|
||||
var search_field: LineEdit = search.search_field
|
||||
search_field.text = "mario"
|
||||
|
||||
search.close()
|
||||
|
||||
assert_str(search_bar.text).is_empty()
|
||||
assert_str(search_field.text).is_empty()
|
||||
|
||||
|
||||
func test_closing_character_select_allows_song_clicks_again() -> void:
|
||||
@@ -129,3 +129,4 @@ func test_toggle_opens_and_closes() -> void:
|
||||
|
||||
shortcuts.toggle()
|
||||
assert_bool(shortcuts.visible).is_false()
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
extends MainWindowTestSuite
|
||||
## The Search popup: typing, the result list, the count, the highlighted letters and
|
||||
## the layout.
|
||||
|
||||
const SOUNDTRACKS: Array[String] = [
|
||||
"Chrono Trigger", "Final Fantasy VI", "Mario & Luigi: Superstar Saga", "Super Mario 64",
|
||||
"The Legend of Zelda: A Link to the Past",
|
||||
]
|
||||
|
||||
var _popup: SearchPopup
|
||||
|
||||
|
||||
func _before_window_starts() -> void:
|
||||
_server.respond_json("/music/all/order", SOUNDTRACKS)
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
super()
|
||||
_popup = _window.get_node("SearchPopup")
|
||||
|
||||
|
||||
func _open() -> void:
|
||||
await _click_at(_window.get_node("TopBar/SearchButton").get_global_rect().get_center())
|
||||
await _runner.simulate_frames(1)
|
||||
|
||||
|
||||
func _type(text: String) -> void:
|
||||
for letter: String in text:
|
||||
_popup.search_field.insert_text_at_caret(letter)
|
||||
_popup.search_field.text_changed.emit(_popup.search_field.text)
|
||||
|
||||
|
||||
func _row_texts() -> Array[String]:
|
||||
var texts: Array[String] = []
|
||||
for row: PanelContainer in _popup.search_results.get_children():
|
||||
if row.visible:
|
||||
texts.append((row.get_child(0) as RichTextLabel).text)
|
||||
return texts
|
||||
|
||||
|
||||
func test_shows_every_soundtrack_with_a_count() -> void:
|
||||
await _open()
|
||||
|
||||
assert_array(_popup.shown_names()).contains_exactly(SOUNDTRACKS)
|
||||
assert_str(_popup.count_label.text).is_equal("5 soundtracks")
|
||||
assert_bool(_popup.empty_label.visible).is_false()
|
||||
assert_bool(_popup.search_field.has_focus()).is_true()
|
||||
|
||||
|
||||
func test_typing_filters_and_counts() -> void:
|
||||
await _open()
|
||||
|
||||
_type("mario")
|
||||
|
||||
assert_array(_popup.shown_names()) \
|
||||
.contains_exactly(["Mario & Luigi: Superstar Saga", "Super Mario 64"])
|
||||
assert_str(_popup.count_label.text).is_equal("2 of 5 soundtracks")
|
||||
|
||||
|
||||
func test_enter_does_not_break_the_search() -> void:
|
||||
await _open()
|
||||
_type("chrono")
|
||||
|
||||
_runner.simulate_key_pressed(KEY_ENTER)
|
||||
await _runner.await_input_processed()
|
||||
|
||||
assert_str(_popup.search_field.text).is_equal("chrono")
|
||||
assert_array(_popup.shown_names()).contains_exactly(["Chrono Trigger"])
|
||||
|
||||
|
||||
func test_no_match_says_so() -> void:
|
||||
await _open()
|
||||
|
||||
_type("metroid")
|
||||
|
||||
assert_array(_popup.shown_names()).is_empty()
|
||||
assert_bool(_popup.empty_label.visible).is_true()
|
||||
assert_str(_popup.count_label.text).is_equal("0 of 5 soundtracks")
|
||||
|
||||
|
||||
func test_short_search_does_not_match_letters_far_apart() -> void:
|
||||
await _open()
|
||||
|
||||
_type("sm")
|
||||
|
||||
assert_array(_popup.shown_names()).is_empty()
|
||||
_type("r")
|
||||
assert_array(_popup.shown_names()).contains_exactly(["Super Mario 64"])
|
||||
|
||||
|
||||
func test_matched_letters_are_highlighted() -> void:
|
||||
await _open()
|
||||
var color: String = _popup.get_theme_color(&"font_color", &"HighlightLarge").to_html(false)
|
||||
|
||||
_type("trig")
|
||||
|
||||
assert_array(_row_texts()).contains_exactly(["Chrono [color=#%s]Trig[/color]ger" % color])
|
||||
|
||||
|
||||
func test_letters_in_order_are_highlighted_one_by_one() -> void:
|
||||
var color: Color = Color.ORANGE
|
||||
var text: String = SearchPopup.highlighted_name("Super Mario 64", "smr", color)
|
||||
var c: String = color.to_html(false)
|
||||
|
||||
assert_str(text).is_equal(
|
||||
"[color=#%s]S[/color]uper [color=#%s]M[/color]a[color=#%s]r[/color]io 64" % [c, c, c])
|
||||
|
||||
|
||||
func test_square_brackets_in_names_are_shown_as_text() -> void:
|
||||
assert_str(SearchPopup.highlighted_name("Disc [1]", "", Color.ORANGE)) \
|
||||
.is_equal("Disc [lb]1]")
|
||||
|
||||
|
||||
func test_clear_shows_everything_again_without_highlights() -> void:
|
||||
await _open()
|
||||
_type("mario")
|
||||
|
||||
await _click_at(_popup.clear_button.get_global_rect().get_center())
|
||||
|
||||
assert_str(_popup.search_field.text).is_empty()
|
||||
assert_array(_row_texts()).contains_exactly(SOUNDTRACKS)
|
||||
|
||||
|
||||
func test_rows_alternate_like_the_other_lists() -> void:
|
||||
await _open()
|
||||
_type("mario")
|
||||
|
||||
var rows: Array[Node] = _popup.search_results.get_children().filter(
|
||||
func(row: Control) -> bool: return row.visible)
|
||||
assert_str(String(rows[0].theme_type_variation)).is_equal("ListRowEven")
|
||||
assert_str(String(rows[1].theme_type_variation)).is_equal("ListRowOdd")
|
||||
|
||||
|
||||
func test_everything_fits_inside_the_panel_at_its_real_size() -> void:
|
||||
await _open()
|
||||
|
||||
var panel: Rect2 = _popup.search_panel.get_global_rect()
|
||||
for node: Control in [_popup.search_field, _popup.clear_button, _popup.count_label,
|
||||
_popup.search_results.get_parent()]:
|
||||
assert_bool(panel.encloses(node.get_global_rect())) \
|
||||
.override_failure_message(node.name + " is outside the panel").is_true()
|
||||
assert_vector(node.scale).is_equal(Vector2.ONE)
|
||||
assert_bool(Rect2(Vector2.ZERO, Vector2(1920, 1080)).encloses(panel)).is_true()
|
||||
@@ -0,0 +1 @@
|
||||
uid://s5ps2kikouh7
|
||||
@@ -34,6 +34,9 @@ const STYLES: Dictionary = {
|
||||
"StatisticsCardHover": "PanelContainer",
|
||||
"StatisticsGrid": "GridContainer",
|
||||
"TabButton": "Button",
|
||||
"LargeButton": "Button",
|
||||
"SearchField": "LineEdit",
|
||||
"SearchResult": "RichTextLabel",
|
||||
}
|
||||
|
||||
## Theme types that only scripts read from, so they are not styles a node can pick.
|
||||
|
||||
Reference in new issue
Block a user