A Soundtracks / Songs switch in the statistics toolbar shows a list of every song with its soundtrack and how often it was played. It is searched on both song and soundtrack names, filtered and sorted like the cards, and clicking a song shows its soundtrack's popup. The server has tens of thousands of songs, so the list only makes the rows on screen and reuses them while scrolling, songs are sorted once per load, and searching waits for a pause in typing. Closes #47
111 lines
3.4 KiB
GDScript
111 lines
3.4 KiB
GDScript
class_name SongStatsList
|
|
extends Control
|
|
## A scrollable list of songs with their soundtrack and how often they were played, under
|
|
## a header naming the columns. Shown in the statistics view instead of the soundtrack
|
|
## cards when Songs is chosen.
|
|
##
|
|
## The server has tens of thousands of songs, so only the rows on screen exist. They are
|
|
## reused for other songs as the list scrolls.
|
|
|
|
## Emitted when a song is clicked.
|
|
signal song_pressed(song: SongInfoForStats)
|
|
|
|
const ROW_SCENE: PackedScene = preload("res://statistics/SongStatsRow.tscn")
|
|
## How many rows one turn of the mouse wheel scrolls.
|
|
const ROWS_PER_WHEEL_STEP: int = 3
|
|
|
|
## The songs in the list, in the order shown.
|
|
var songs: Array[SongInfoForStats] = []
|
|
|
|
# The rows that fit on screen, top to bottom
|
|
var _rows: Array[SongStatsRow] = []
|
|
var _row_height: float = 0.0
|
|
var _search_text: String = ""
|
|
|
|
@onready var rows_area: Control = %RowsArea
|
|
@onready var scroll_bar: VScrollBar = %ScrollBar
|
|
|
|
|
|
func _ready() -> void:
|
|
var measured: SongStatsRow = ROW_SCENE.instantiate()
|
|
add_child(measured)
|
|
_row_height = measured.get_combined_minimum_size().y
|
|
measured.free()
|
|
rows_area.resized.connect(_fit_rows)
|
|
scroll_bar.value_changed.connect(func(_value: float) -> void: _show_rows())
|
|
rows_area.gui_input.connect(_on_gui_input)
|
|
_fit_rows()
|
|
|
|
|
|
## Shows [param new_songs] from the top, with the letters [param search_text] matched
|
|
## highlighted.
|
|
func show_songs(new_songs: Array[SongInfoForStats], search_text: String = "") -> void:
|
|
songs = new_songs
|
|
_search_text = search_text
|
|
_update_scroll_bar()
|
|
scroll_bar.value = 0
|
|
_show_rows()
|
|
|
|
|
|
## The rows showing a song now, top to bottom.
|
|
func shown_rows() -> Array[SongStatsRow]:
|
|
var shown: Array[SongStatsRow] = []
|
|
for row: SongStatsRow in _rows:
|
|
if row.visible:
|
|
shown.append(row)
|
|
return shown
|
|
|
|
|
|
## Scrolls so song number [param index] is at the top, as far as the list goes.
|
|
func scroll_to(index: int) -> void:
|
|
scroll_bar.value = index * _row_height
|
|
|
|
|
|
# Makes as many rows as fit on screen, plus one for the row partly scrolled out
|
|
func _fit_rows() -> void:
|
|
var needed: int = ceili(rows_area.size.y / _row_height) + 1
|
|
while _rows.size() < needed:
|
|
var row: SongStatsRow = ROW_SCENE.instantiate()
|
|
rows_area.add_child(row)
|
|
row.pressed.connect(song_pressed.emit)
|
|
_rows.append(row)
|
|
while _rows.size() > needed:
|
|
_rows.pop_back().free()
|
|
for row: SongStatsRow in _rows:
|
|
row.size = Vector2(rows_area.size.x, _row_height)
|
|
_update_scroll_bar()
|
|
_show_rows()
|
|
|
|
|
|
func _update_scroll_bar() -> void:
|
|
scroll_bar.max_value = songs.size() * _row_height
|
|
scroll_bar.page = rows_area.size.y
|
|
scroll_bar.step = 0
|
|
scroll_bar.visible = scroll_bar.max_value > scroll_bar.page
|
|
|
|
|
|
func _show_rows() -> void:
|
|
var first: int = floori(scroll_bar.value / _row_height)
|
|
var offset: float = scroll_bar.value - first * _row_height
|
|
for i: int in _rows.size():
|
|
var row: SongStatsRow = _rows[i]
|
|
var index: int = first + i
|
|
row.visible = index < songs.size()
|
|
if row.visible:
|
|
row.position = Vector2(0, i * _row_height - offset)
|
|
row.show_song(songs[index], index, _search_text)
|
|
|
|
|
|
func _on_gui_input(event: InputEvent) -> void:
|
|
if !(event is InputEventMouseButton) || !event.pressed:
|
|
return
|
|
var step: float = _row_height * ROWS_PER_WHEEL_STEP
|
|
match event.button_index:
|
|
MOUSE_BUTTON_WHEEL_UP:
|
|
scroll_bar.value -= step * event.factor if event.factor > 0 else step
|
|
MOUSE_BUTTON_WHEEL_DOWN:
|
|
scroll_bar.value += step * event.factor if event.factor > 0 else step
|
|
_:
|
|
return
|
|
rows_area.accept_event()
|