use crate::load_song::extract_cover; use crate::load_song::load_song; use crate::lyrics::Lyrics; use crate::file_select::FileSelector; use crate::styles::Theme; use rfd::AsyncFileDialog; use std::path::PathBuf; use core::time::Duration; use iced::Subscription; use iced::Clipboard; use iced::Command; use iced::Application; use iced::Container; use iced::Row; use iced::Element; use iced::Length; use iced::Align; use iced::executor; use iced_futures::time; use iced_native::subscription; use iced_native::keyboard; use iced_native::window; use iced_native::event::Event; #[derive(Clone, Debug)] pub struct DelyriumApp { lyrics_component: Lyrics, theme: Theme, mode: AppMode, file_selector: FileSelector, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum AppMode { FileSelect, Main } #[derive(Clone, Debug)] pub enum Message { LyricChanged { line_no: usize, new_value: String, }, LineAdvanced(usize), PasteSent, Tick, PromptForFile, FileOpened(PathBuf), Null, } impl Application for DelyriumApp { type Message = Message; type Executor = executor::Default; type Flags = (); fn new(_: Self::Flags) -> (Self, Command) { ( DelyriumApp { lyrics_component: Lyrics::new(), theme: Theme::default(), mode: AppMode::FileSelect, file_selector: FileSelector::default(), }, Command::none(), ) } fn title(&self) -> String { String::from("Delyrium") } fn update(&mut self, message: Message, clipboard: &mut Clipboard) -> Command{ let mut command = None; match message { Message::LyricChanged { line_no, new_value } => { self.lyrics_component.update_line(new_value, line_no); }, Message::LineAdvanced(current_line) => { self.lyrics_component.advance_line(current_line); }, Message::PasteSent => { let clip_text = clipboard.read().unwrap_or(String::new()); let clip_pasted_len = clip_text.chars() .filter(|c| *c != '\r' && *c != '\n') .count(); let line = self.lyrics_component.current_line_mut().1; line.value.truncate(line.value.len() - clip_pasted_len); self.lyrics_component.insert_text(clip_text); }, Message::Tick => { match self.mode { AppMode::FileSelect => { self.file_selector.tick(); }, _ => { }, } }, Message::FileOpened(path) => { println!("File opened! {}", path.display()); let mut song = load_song(&path).unwrap(); extract_cover(&mut song); }, Message::PromptForFile => { let task = async { let handle = AsyncFileDialog::new() .add_filter("Song Files", &["mp3", "flac", "ogg", "opus", "wav", "mkv"]) .set_title("Select a song") .pick_file() .await; if let Some(h) = handle { Message::FileOpened(h.path().to_owned()) } else { Message::Null } }; command = Some(task.into()); }, Message::Null => { }, } command.unwrap_or_else(Command::none) } fn view(&mut self) -> Element { match self.mode { AppMode::Main => { Container::new( Row::new() .push(self.lyrics_component.view(self.theme)) ) .align_y(Align::Center) .style(self.theme) .height(Length::Fill) .into() }, AppMode::FileSelect => { self.file_selector.view() } } } fn subscription(&self) -> Subscription { let runtime_events = subscription::events_with(|event, _| { match event { Event::Keyboard(keyboard::Event::KeyPressed {key_code, modifiers}) => { match (key_code, modifiers) { ( keyboard::KeyCode::V, keyboard::Modifiers { control, .. } ) if control == true => { Some(Message::PasteSent) } _ => { None } } }, Event::Window(window::Event::FileDropped(path)) => { Some(Message::FileOpened(path)) }, _ => { None } } }); let fps30 = time::every(Duration::from_millis(1000 / 30)).map(|_| Message::Tick); match self.mode { AppMode::FileSelect => { Subscription::batch([ runtime_events, fps30 ]) }, AppMode::Main => runtime_events, } } }