Cards only list songs that have been played. With fewer than six played songs they are all listed as most played, so no song is listed twice. The soundtrack popup still lists every song. Closes #46
131 lines
4.9 KiB
GDScript
131 lines
4.9 KiB
GDScript
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)
|