lyrs/src/model/display.rs

130 lines
3.8 KiB
Rust
Raw Normal View History

2024-07-08 22:58:15 -05:00
use std::collections::VecDeque;
2024-07-07 22:44:39 -05:00
use super::song::{Plan, Song, Verse};
2024-07-06 18:18:34 -05:00
pub struct PlaylistEntry {
pub song: Song,
2024-07-08 22:58:15 -05:00
pub plan_name: Option<String>,
2024-07-06 18:18:34 -05:00
}
pub struct PlaylistVerseRef {
pub song_index: usize,
pub map_verse_index: usize,
}
pub struct Display {
2024-07-08 22:58:15 -05:00
pub playlist: VecDeque<PlaylistEntry>,
2024-07-07 22:44:39 -05:00
current: PlaylistVerseRef,
frozen_at: Option<PlaylistVerseRef>,
2024-07-06 18:18:34 -05:00
pub blanked: bool,
}
2024-07-07 22:44:39 -05:00
impl Display {
pub fn verse_ref(&self) -> &PlaylistVerseRef {
self.frozen_at.as_ref().unwrap_or(&self.current)
}
pub fn verse(&self) -> Option<&Verse> {
if self.blanked {
None
} else {
2024-07-08 22:58:15 -05:00
let verse_ref = self.verse_ref();
let entry = self.entry(verse_ref)?;
entry.song.verses.get(self.verse_name(verse_ref)?)
}
}
pub fn entry(&self, verse_ref: &PlaylistVerseRef) -> Option<&PlaylistEntry> {
self.playlist.get(verse_ref.song_index)
}
pub fn plan(&self, verse_ref: &PlaylistVerseRef) -> Option<&Plan> {
let PlaylistEntry { song, plan_name } = self.entry(verse_ref)?;
// TODO: this could "fail silently" and use the default plan
Some(song.plan(plan_name.as_deref()))
}
pub fn verse_name(&self, verse_ref: &PlaylistVerseRef) -> Option<&String> {
self.plan(verse_ref)?.get(verse_ref.map_verse_index)
}
pub fn clamp(&mut self) {
// ensure all plan names exist or fallback to defaults
for p in self.playlist.iter_mut() {
if let Some(plan_name) = p.plan_name.as_mut() {
if !p.song.other_plans.contains_key(plan_name) {
p.plan_name = None;
}
}
}
// ensure song index is within bounds or be 0
self.current.song_index = self.current.song_index.clamp(0, self.playlist.len());
if let Some(frozen_at) = self.frozen_at.as_mut() {
frozen_at.song_index = frozen_at.song_index.clamp(0, self.playlist.len());
}
// ensure map verse index is within bounds or be 0
if let Some(plan) = self.plan(&self.current) {
self.current.map_verse_index = self.current.map_verse_index.clamp(0, plan.len());
}
let new_frozen_map_verse_index = match &self.frozen_at {
Some(frozen_at) => {
if let Some(plan) = self.plan(&frozen_at) {
frozen_at.map_verse_index.clamp(0, plan.len())
} else {
0
}
}
None => 0,
};
if let Some(frozen_at) = self.frozen_at.as_mut() {
frozen_at.map_verse_index = new_frozen_map_verse_index
}
}
}
mod test {
use super::*;
fn default(playlist: VecDeque<PlaylistEntry>) -> Display {
let current = PlaylistVerseRef {
song_index: 0,
map_verse_index: 0,
};
Display {
playlist,
current,
frozen_at: None,
blanked: false,
2024-07-07 22:44:39 -05:00
}
}
2024-07-08 22:58:15 -05:00
fn two_song_playlist() -> VecDeque<PlaylistEntry> {
VecDeque::from([
PlaylistEntry {
song: "Song1\n\ns1verse1\n\ns1verse2".parse().unwrap(),
plan_name: None,
},
PlaylistEntry {
song: "Song2\n\ns2verse1\n\ns2verse2".parse().unwrap(),
plan_name: None,
},
])
2024-07-07 22:44:39 -05:00
}
2024-07-08 22:58:15 -05:00
#[test]
fn displays_no_verse_when_empty() {
let playlist: VecDeque<PlaylistEntry> = VecDeque::from(vec![]);
let display = default(playlist);
assert_eq!(display.verse(), None)
}
#[test]
fn displays_the_first_verse_when_a_song_is_there() {
let display = default(two_song_playlist());
assert_eq!(display.verse().unwrap().content, "s1verse1")
}
2024-07-07 22:44:39 -05:00
}