use crate::lyrics::LyricEvent; use crate::controls::ControlsEvent; use crate::load_song::load_song; use crate::editor::Editor; use crate::file_select::FileSelector; 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::Element; use iced::executor; use iced_futures::time; use iced_native::subscription; use iced_native::keyboard; use iced_native::window; use iced_native::event::Event; pub struct DelyriumApp { lyrics_component: Option, file_selector: FileSelector, size: (u32, u32), } #[derive(Clone, Debug)] pub enum Message { LyricEvent { line_no: usize, kind: LyricEvent, }, PasteSent, Tick, PromptForFile, FileOpened(PathBuf), Resized(u32, u32), ControlsEvent(ControlsEvent), Null, } impl Application for DelyriumApp { type Message = Message; type Executor = executor::Default; type Flags = (); fn new(_: Self::Flags) -> (Self, Command) { ( DelyriumApp { lyrics_component: None, file_selector: FileSelector::default(), size: (0, 0), }, 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::LyricEvent { line_no, kind } => { if let Some(lyrics) = self.lyrics_component.as_mut() { lyrics.handle_lyric_event(line_no, kind); } }, Message::PasteSent => { if let Some(lyrics) = self.lyrics_component.as_mut() { #[allow(clippy::or_fun_call)] // This is a const 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 = lyrics.current_line_mut().1; line.value.truncate(line.value.len() - clip_pasted_len); lyrics.insert_text(clip_text); } }, Message::Tick => { if self.lyrics_component.is_none() { self.file_selector.tick(); } }, Message::FileOpened(path) => { println!("File opened! {}", path.display()); let song = load_song(&path).unwrap().format; let (editor, cmd) = Editor::new(song, self.size); self.lyrics_component = Some(editor); command = Some(cmd); }, 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::Resized(w, h) => { self.size = (w, h); if let Some(lyrics) = self.lyrics_component.as_mut() { lyrics.notify_resized(w, h); } }, Message::ControlsEvent(e) => { if let Some(lyrics) = self.lyrics_component.as_mut() { lyrics.handle_controls_event(e); } }, Message::Null => { }, } command.unwrap_or_else(Command::none) } fn view(&mut self) -> Element { if let Some(lyrics) = self.lyrics_component.as_mut() { lyrics.view() } else { 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 => { Some(Message::PasteSent) } _ => { None } } }, Event::Window(window::Event::FileDropped(path)) => { Some(Message::FileOpened(path)) }, Event::Window(window::Event::Resized{width,height}) => { Some(Message::Resized(width,height)) }, _ => { None } } }); let is_animating = if let Some(editor) = &self.lyrics_component { editor.is_animating() } else { true }; let fps30 = if is_animating { time::every(Duration::from_millis(1000 / 30)).map(|_| Message::Tick) } else { Subscription::none() }; Subscription::batch([ runtime_events, fps30 ]) } }