use iced::{
alignment,
widget::{
scrollable, text, text_editor::Binding, Button, Column, Container, MouseArea, Row, Space,
},
Color, Element, Length,
};
use super::{
shared_components::{empty_state, loading_state, scope_button, scope_tab_row},
styles::{button_style_panel_item, fg_secondary},
theme, Message, MusicPlayer,
};
use crate::{
app::{LyricsState, LyricsViewMode},
load_state::LoadState,
theme::AppTheme,
};
pub(super) fn view_lyrics<'a>(
player: &'a MusicPlayer,
lyrics_state: &'a LyricsState,
) -> Element<'a, Message, AppTheme> {
let track = player.queue.current();
let lyrics_ready = matches!(&lyrics_state.lyrics, LoadState::Ready(_));
let body: Element<'a, Message, AppTheme> = if lyrics_state.mode == LyricsViewMode::Selectable
&& lyrics_ready
{
view_select_editor(&lyrics_state.editor)
} else {
let mode = lyrics_state.mode;
let lyrics_state = &lyrics_state.lyrics;
match (track, lyrics_state) {
(Some(_), LoadState::Ready(lyrics))
if !lyrics.timed.is_empty() && mode == LyricsViewMode::Synced =>
{
view_synced(player, lyrics)
}
(Some(_), LoadState::Ready(lyrics)) => Container::new(
text(lyrics.plain.clone())
.size(theme::TEXT_SIZE_LG)
.center()
.style(fg_secondary())
.width(Length::Fill),
)
.padding(theme::SPACING_LG)
.into(),
(Some(_), LoadState::Loading) => loading_state(player.strings.looking_up_lyrics),
(Some(_), LoadState::Failed(e)) => empty_state((player.strings.couldnt_load_lyrics)(e)),
(None, _) => empty_state(player.strings.play_a_track_for_lyrics),
}
};
let body: Element<'a, Message, AppTheme> = MouseArea::new(body)
.on_right_press(Message::CopyLyrics)
.into();
Column::with_children([
Container::new(body).height(Length::Fill).into(),
view_bottom_controls(player, lyrics_state).into(),
])
.spacing(theme::SPACING_MD)
.into()
}
fn view_bottom_controls<'a>(
player: &'a MusicPlayer,
lyrics_state: &'a LyricsState,
) -> Row<'a, Message, AppTheme> {
const MODES: [LyricsViewMode; 3] = [
LyricsViewMode::Selectable,
LyricsViewMode::Synced,
LyricsViewMode::Plain,
];
let labels = [
player.strings.lyrics_selectable,
player.strings.lyrics_synced,
player.strings.lyrics_plain,
];
let picker = Row::with_children(MODES.iter().zip(labels).map(|(&mode, label)| {
let selected = lyrics_state.mode == mode;
let available = lyrics_state.mode_available(mode);
scope_button(label, selected)
.on_press_maybe(available.then_some(Message::SetLyricsViewMode(mode)))
.into()
}))
.spacing(theme::SPACING_XS);
let selected_provider = player.lyrics_client.selected();
let provider_row = scope_tab_row(crate::lyrics::LyricsProvider::all().iter().map(|provider| {
(
provider.name().to_string(),
*provider == selected_provider,
Message::SelectLyricsProvider(*provider),
)
}));
Row::with_children([
provider_row,
Space::new().width(Length::Fill).into(),
picker.into(),
])
.padding(theme::SPACING_SM)
.align_y(alignment::Vertical::Center)
}
fn view_select_editor(
editor_content: &iced::widget::text_editor::Content,
) -> Element<'_, Message, AppTheme> {
iced::widget::text_editor(editor_content)
.on_action(Message::LyricsEditorAction)
.key_binding(|press| {
let binding = Binding::from_key_press(press)?;
match binding {
Binding::Copy
| Binding::Move(_)
| Binding::Select(_)
| Binding::SelectWord
| Binding::SelectLine
| Binding::SelectAll
| Binding::Unfocus => Some(binding),
_ => None,
}
})
.style(|theme: &AppTheme, _| {
let p = &theme.palette;
iced::widget::text_editor::Style {
background: Color::TRANSPARENT.into(),
border: iced::Border::default(),
placeholder: Color::TRANSPARENT,
value: p.fg_secondary,
selection: p.accent.scale_alpha(0.4),
}
})
.padding(theme::SPACING_LG)
.into()
}
fn view_synced<'a>(
player: &'a MusicPlayer,
lyrics: &'a crate::lyrics::Lyrics,
) -> Element<'a, Message, AppTheme> {
let position = player.progress * player.duration;
let active = lyrics.active_index(position);
let lines = lyrics.timed.iter().enumerate().map(|(i, (secs, line))| {
let is_active = active == Some(i);
let centered = Container::new(text(line).size(theme::TEXT_SIZE_XL)).center(Length::Fill);
Button::new(centered)
.padding([theme::SPACING_SM, theme::SPACING_LG])
.style(button_style_panel_item(is_active))
.on_press(Message::LyricsLineClicked(*secs))
.into()
});
scrollable(
Column::with_children(lines)
.spacing(theme::SPACING_SM)
.padding(theme::SPACING_LG),
)
.into()
}