Show soundtrack covers and edit alternative names (#49)
Covers: statistics cards show the soundtrack's cover on the left, and the
statistics popup shows it large. Until it arrives, and without a cover, the
soundtrack's initials stand in. Covers load only for cards on or near the
screen, three at a time, and are cached in covers/ next to the settings
(CoverCache), downloaded again only when cover_modified changes. The index and
file handling are shared with CharacterCache in a new FileCache.
Alternative names: the popup shows them and has an editor (add, remove, save)
that sends PUT /soundtracks/{uuid}/alternative-names. The statistics search and
the Search popup find soundtracks by them; Search shows the matching name after
the real one. Search now loads /soundtracks, falling back to the plain list.
The API types are regenerated from the server's spec, with a SoundtrackCover
theme style in every theme.
Closes #49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a735c515bf
commit
ef6ac8f1fd
37 files changed
+8029
-1763
No files matched your search
+36
-11
@@ -1,7 +1,8 @@
|
||||
class_name SearchPopup
|
||||
extends BasePopup
|
||||
## Searches every soundtrack on the server as you type, with [SoundtrackSearch], and
|
||||
## highlights the letters that matched.
|
||||
## highlights the letters that matched. Soundtracks are also found by their alternative
|
||||
## names; the alternative name that matched is shown after the real name.
|
||||
|
||||
## Every soundtrack on the server, sorted by name.
|
||||
var soundtracks: Array = []
|
||||
@@ -10,6 +11,8 @@ var _matcher: SoundtrackSearch = SoundtrackSearch.new()
|
||||
# One row per soundtrack, in the order of soundtracks. Rows are reused between searches.
|
||||
var _rows: Array[PanelContainer] = []
|
||||
var _index_of: Dictionary = {}
|
||||
# The other names of each soundtrack, by its real name
|
||||
var _alternatives: Dictionary = {}
|
||||
|
||||
@onready var search_panel: Panel = %SearchPanel
|
||||
@onready var search_field: LineEdit = %SearchField
|
||||
@@ -86,19 +89,25 @@ func get_list_of_soundtracks() -> void:
|
||||
if Settings.is_offline():
|
||||
set_soundtracks(Settings.music_folder.soundtrack_names())
|
||||
return
|
||||
var handle_soundtracks: Callable = func handle_soundtracks(array: Variant) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
set_soundtracks(array)
|
||||
else:
|
||||
GameLog.warning("Unexpected data")
|
||||
MusicServerClient.fetch_all_soundtracks_ordered(handle_soundtracks)
|
||||
var with_names: Callable = func with_names(list: Array[Soundtrack]) -> void:
|
||||
var names: Array[String] = []
|
||||
var alternatives: Dictionary = {}
|
||||
for soundtrack: Soundtrack in list:
|
||||
names.append(soundtrack.name)
|
||||
alternatives[soundtrack.name] = soundtrack.alternative_names
|
||||
names.sort_custom(func(a: String, b: String) -> bool:
|
||||
return a.naturalnocasecmp_to(b) < 0)
|
||||
set_soundtracks(names, alternatives)
|
||||
MusicServerClient.fetch_soundtracks(with_names, _load_names_only)
|
||||
|
||||
|
||||
## Replaces the searchable soundtracks and shows them, keeping the current search.
|
||||
func set_soundtracks(list: Array) -> void:
|
||||
## [param alternatives] has the other names of a soundtrack, by its real name.
|
||||
func set_soundtracks(list: Array, alternatives: Dictionary = {}) -> void:
|
||||
soundtracks = []
|
||||
soundtracks.append_array(list)
|
||||
_matcher.set_names(soundtracks)
|
||||
_alternatives = alternatives
|
||||
_matcher.set_names(soundtracks, alternatives)
|
||||
_index_of.clear()
|
||||
for index: int in range(soundtracks.size() - 1, -1, -1):
|
||||
_index_of[soundtracks[index]] = index
|
||||
@@ -145,9 +154,25 @@ func _make_row() -> PanelContainer:
|
||||
return row
|
||||
|
||||
|
||||
# Servers without /soundtracks only list the names
|
||||
func _load_names_only() -> void:
|
||||
MusicServerClient.fetch_all_soundtracks_ordered(func(array: Variant) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
set_soundtracks(array)
|
||||
else:
|
||||
GameLog.warning("Unexpected data"))
|
||||
|
||||
|
||||
func _show_name(row: PanelContainer, soundtrack_name: String, search_text: String) -> void:
|
||||
var text: String = highlighted_name(soundtrack_name, search_text,
|
||||
get_theme_color(&"font_color", &"HighlightLarge"))
|
||||
var highlight: Color = get_theme_color(&"font_color", &"HighlightLarge")
|
||||
var text: String = highlighted_name(soundtrack_name, search_text, highlight)
|
||||
# Found by another name: show which, dimmer, after the real name
|
||||
var alternative: String = _matcher.matched_alternative(soundtrack_name) \
|
||||
if !search_text.is_empty() else ""
|
||||
if !alternative.is_empty():
|
||||
text += " [color=#%s]%s[/color]" % [
|
||||
get_theme_color(&"default_color", &"SongStatsSecondary").to_html(false),
|
||||
highlighted_name(alternative, search_text, highlight)]
|
||||
var label: RichTextLabel = row.get_child(0)
|
||||
# Setting the same text again would make the label lay it out again for nothing
|
||||
if label.text != text:
|
||||
|
||||
Reference in new issue
Block a user