class_name SoundtrackCard extends PanelContainer ## One soundtrack in the statistics view: its name, how often and when it was last ## played, and its most and least played songs. Songs never played are not listed, and ## the least played are only shown when there are enough played songs to not repeat ## any. Clicking it asks for all its songs. ## Emitted when the card is clicked. signal pressed(soundtrack: SoundtrackWithSongs) ## How many of the most and least played songs the card lists. With fewer than twice ## as many played songs, all of them are listed as most played. const SONGS_SHOWN: int = 3 ## The soundtrack the card shows. var soundtrack: SoundtrackWithSongs @onready var name_label: Label = %NameLabel @onready var played_label: Label = %PlayedLabel @onready var last_played_label: Label = %LastPlayedLabel @onready var most_played_header: Label = %MostPlayedHeader @onready var most_played_list: VBoxContainer = %MostPlayedList @onready var least_played_header: Label = %LeastPlayedHeader @onready var least_played_list: VBoxContainer = %LeastPlayedList func _ready() -> void: mouse_entered.connect(_set_hovered.bind(true)) mouse_exited.connect(_set_hovered.bind(false)) gui_input.connect(_on_gui_input) ## Shows [param data] on the card. func show_soundtrack(data: SoundtrackWithSongs) -> void: soundtrack = data name_label.text = data.soundtrack_name tooltip_text = data.soundtrack_name played_label.text = played_text(data.soundtrack_played) last_played_label.text = last_played_text(data.soundtrack_last_played) last_played_label.visible = !data.soundtrack_last_played.is_empty() var most_played: Array[SongInfoForStats] = [] var least_played: Array[SongInfoForStats] = [] var played: Array[SongInfoForStats] = played_songs(data.songs) if played.size() >= SONGS_SHOWN * 2: most_played = played.slice(0, SONGS_SHOWN) least_played = songs_by_plays(played, true).slice(0, SONGS_SHOWN) else: # Too few to split without listing a song twice, so all of them are most played most_played = played most_played_header.text = song_list_header(data.songs.size(), played.size()) _fill_song_list(most_played_list, most_played) least_played_header.visible = !least_played.is_empty() _fill_song_list(least_played_list, least_played) ## "Played 3 times", or "Never played" for [code]0[/code]. static func played_text(times_played: int) -> String: if times_played == 0: return "Never played" return "Played %d %s" % [times_played, "time" if times_played == 1 else "times"] ## "Last played 2026-10-01" from the server's date and time, or an empty String when ## there is none. static func last_played_text(last_played: String) -> String: if last_played.is_empty(): return "" return "Last played " + last_played.substr(0, 10) ## The songs of [param songs] that have been played, most played first. Songs never ## played are left out: the card only lists them in the popup with every song. static func played_songs(songs: Array[SongInfoForStats]) -> Array[SongInfoForStats]: var played: Array[SongInfoForStats] = [] played.assign(songs_by_plays(songs).filter( func(song: SongInfoForStats) -> bool: return song.times_played > 0)) return played ## The header above the most played songs, for a soundtrack with [param song_count] ## songs of which [param played_count] have been played. static func song_list_header(song_count: int, played_count: int) -> String: if song_count == 0: return "No songs" if played_count == 0: return "No songs played yet" return "Most played" ## One line of a song list: how often the song was played, then its name. static func song_text(song: SongInfoForStats) -> String: return "%d × %s" % [song.times_played, song.song_name] ## [param songs] sorted with the most played first, or the least played first with ## [param least_first], and by name when played as often. static func songs_by_plays( songs: Array[SongInfoForStats], least_first: bool = false) -> Array[SongInfoForStats]: var sorted: Array[SongInfoForStats] = songs.duplicate() sorted.sort_custom(func(a: SongInfoForStats, b: SongInfoForStats) -> bool: if a.times_played != b.times_played: return (a.times_played < b.times_played) == least_first return a.song_name.naturalnocasecmp_to(b.song_name) < 0 ) return sorted func _fill_song_list(list: VBoxContainer, songs: Array[SongInfoForStats]) -> void: while list.get_child_count() < songs.size(): var label: Label = Label.new() label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS list.add_child(label) for i: int in list.get_child_count(): var label: Label = list.get_child(i) label.visible = i < songs.size() if label.visible: label.text = song_text(songs[i]) func _set_hovered(hovered: bool) -> void: theme_type_variation = &"StatisticsCardHover" if hovered else &"StatisticsCard" func _on_gui_input(event: InputEvent) -> void: if event is InputEventMouseButton && event.pressed \ && event.button_index == MOUSE_BUTTON_LEFT: accept_event() pressed.emit(soundtrack)