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. 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. 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] = songs_by_plays(data.songs).slice(0, SONGS_SHOWN) # Without the songs already shown as most played var least_played: Array[SongInfoForStats] = songs_by_plays(data.songs, true).filter( func(song: SongInfoForStats) -> bool: return !most_played.has(song) ).slice(0, SONGS_SHOWN) most_played_header.text = "Most played" if !data.songs.is_empty() else "No songs" _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) ## 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)