diff options
author | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-01-02 18:39:43 +0100 |
---|---|---|
committer | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-01-02 18:39:43 +0100 |
commit | ee94b47c80a6d580119104b1b3ce16491c090a73 (patch) | |
tree | 2ab160f2e237a91e65cc19cef8fb41ad05ff779c | |
parent | 2df46f83518d1e8d1603b71ee6bb4962cd4517d7 (diff) |
instance-oriented refactoring
-rw-r--r-- | src/main.rs | 630 |
1 files changed, 332 insertions, 298 deletions
diff --git a/src/main.rs b/src/main.rs index 8870690..a2d2de7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,6 @@ use tui::style::{Color, Style}; use tui::widgets::{Block, Borders, Gauge, List, ListItem, ListState, Paragraph}; use tui::{backend::CrosstermBackend, Terminal}; -static VOLUME: Once = Once::new(); - #[derive(Debug, Parser)] #[command(author = "Himbeer", version = "v0.1.0", about = "A custom music player for the command line, written in Rust.", long_about = None)] struct Args { @@ -68,352 +66,388 @@ struct AutoplayState { shuffle: bool, } -fn subsize(area: Rect, i: u16) -> Rect { - let mut new_area = area; - new_area.y += i * area.height; - - new_area +struct Instance { + args: Args, + cursor_state: CursorState, + autoplay_state: AutoplayState, + play: Play, + files: Vec<PathBuf>, + list_state: ListState, + volume_once: Once, } -fn is_paused(play: &Play) -> bool { - match play.position() { - Some(position) => match play.position() { - Some(new_pos) => position.nseconds() == new_pos.nseconds(), +impl Instance { + fn dir(&self) -> String { + self.args.dir.clone().unwrap_or_else(|| String::from(".")) + } + + fn is_paused(&self) -> bool { + match self.play.position() { + Some(position) => match self.play.position() { + Some(new_pos) => position.nseconds() == new_pos.nseconds(), + None => true, + }, None => true, - }, - None => true, + } } -} -fn play_path<T: fmt::Display>(play: &Play, path: T, init_volume: Option<f64>) { - let uri = format!("file://{}", path); + fn play_path<T: fmt::Display>(&self, path: T) { + let uri = format!("file://{}", path); - play.set_uri(Some(&uri)); - play.play(); + self.play.set_uri(Some(&uri)); + self.play.play(); - if let Some(init_volume) = init_volume { - thread::sleep(Duration::from_millis(500)); + if let Some(init_volume) = self.args.volume { + thread::sleep(Duration::from_millis(500)); - VOLUME.call_once(|| { - play.set_volume(init_volume); - }); + self.volume_once.call_once(|| { + self.play.set_volume(init_volume); + }); + } } -} -/// Get the progress ratio of the current song. -/// Returns 0.0 if no song is selected. -fn current_progress(play: &Play) -> f64 { - if let Some(position) = play.position() { - if let Some(duration) = play.duration() { - position.seconds() as f64 / duration.seconds() as f64 + /// Get the progress ratio of the current song. + /// Returns 0.0 if no song is selected. + fn current_progress(&self) -> f64 { + if let Some(position) = self.play.position() { + if let Some(duration) = self.play.duration() { + position.seconds() as f64 / duration.seconds() as f64 + } else { + 0.0 + } } else { 0.0 } - } else { - 0.0 } -} -fn main() -> anyhow::Result<()> { - let args = Args::parse(); + fn new() -> anyhow::Result<Self> { + let mut instance = Self { + args: Args::parse(), + cursor_state: CursorState::default(), + autoplay_state: AutoplayState::default(), + play: Play::new(PlayVideoRenderer::NONE), + files: Vec::new(), + list_state: ListState::default(), + volume_once: Once::new(), + }; - let mut cursor_state = CursorState::default(); - let mut autoplay_state = AutoplayState::default(); + instance.files = fs::read_dir(instance.dir())? + .map(|e| e.unwrap().path()) + .collect(); + instance.files.sort(); + instance.list_state.select(Some(0)); - gstreamer::init()?; - enable_raw_mode()?; + Ok(instance) + } - let play = Play::new(PlayVideoRenderer::NONE); + fn run(&mut self) -> anyhow::Result<()> { + enable_raw_mode()?; - let stdout = io::stdout(); - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; + self.play = Play::new(PlayVideoRenderer::NONE); + let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?; - let dir = args.dir.unwrap_or_else(|| String::from(".")); + if let Some(initial) = &self.args.play { + self.play_path(initial); + } else if self.args.random { + let track = rand::random::<usize>() % self.files.len(); + self.play_path(self.files[track].display()); + } - let mut files: Vec<PathBuf> = fs::read_dir(dir)?.map(|e| e.unwrap().path()).collect(); + loop { + terminal.draw(|f| { + let main_style = Style::default().bg(Color::Reset).fg(Color::Magenta); + let focused_style = main_style.fg(Color::Cyan); - files.sort(); + let sizes = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(f.size().width / 2), Constraint::Min(0)]) + .split(f.size()); - let mut list_state = ListState::default(); - list_state.select(Some(0)); + let listing_size = sizes[0]; + let status_size = sizes[1]; - if let Some(initial) = args.play { - play_path(&play, initial, args.volume); - } else if args.random { - let track = rand::random::<usize>() % files.len(); - play_path(&play, files[track].display(), args.volume); - } + let files: Vec<ListItem> = self.files + .iter() + .map(|e| ListItem::new(e.file_name().unwrap().to_str().unwrap())) + .collect(); - loop { - terminal.draw(|f| { - let main_style = Style::default().bg(Color::Reset).fg(Color::Magenta); - let focused_style = main_style.fg(Color::Cyan); - - let sizes = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Length(f.size().width / 2), Constraint::Min(0)]) - .split(f.size()); - - let listing_size = sizes[0]; - let status_size = sizes[1]; - - let files: Vec<ListItem> = files - .iter() - .map(|e| ListItem::new(e.file_name().unwrap().to_str().unwrap())) - .collect(); - - let highlight_base_style = match cursor_state { - CursorState::MusicList => focused_style, - _ => main_style, - }; - - let block = Block::default().title("Select music").borders(Borders::ALL); - let listing = List::new(files) - .block(block) - .style(match cursor_state { + let highlight_base_style = match self.cursor_state { CursorState::MusicList => focused_style, _ => main_style, - }) - .highlight_style( - highlight_base_style - .bg(highlight_base_style.fg.unwrap()) - .fg(Color::Black), - ) - .highlight_symbol("> "); - - let status_title = match play.uri() { - Some(uri) => { - String::from("Now playing: ") + uri.as_str().split('/').next_back().unwrap() + }; + + let block = Block::default().title("Select music").borders(Borders::ALL); + let listing = List::new(files) + .block(block) + .style(match self.cursor_state { + CursorState::MusicList => focused_style, + _ => main_style, + }) + .highlight_style( + highlight_base_style + .bg(highlight_base_style.fg.unwrap()) + .fg(Color::Black), + ) + .highlight_symbol("> "); + + let status_title = match self.play.uri() { + Some(uri) => { + String::from("Now playing: ") + uri.as_str().split('/').next_back().unwrap() + } + None => String::from("Idle"), + }; + + let status_block = Block::default() + .title(status_title) + .borders(Borders::ALL) + .style(main_style); + let status_sizes = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(status_size.height / 10), + Constraint::Min(0), + ]) + .margin(1) + .split(status_size)[0]; + + let volume_size = subsize(status_sizes, 0); + let progress_size = subsize(status_sizes, 1); + let control_size = subsize(status_sizes, 2); + + let block = Block::default().title("Volume").borders(Borders::ALL); + let volume_gauge = Gauge::default() + .block(block) + .style(match self.cursor_state { + CursorState::Volume => focused_style, + _ => main_style, + }) + .gauge_style(main_style.fg(Color::Blue)) + .ratio(self.play.volume()); + + let block = Block::default().borders(Borders::ALL); + let progress_gauge = Gauge::default() + .block(block) + .style(main_style) + .gauge_style(main_style.fg(Color::Blue)) + .ratio(self.current_progress()); + + let control_buttons = if self.is_paused() { + String::from( + "[ 🔁 ] [ 🔂 ] [ ⏮ ] [ ◀ ] [ ▶ ] [ ▶ ] [ ⏭ ] [ ⏬ ] [ 🔀 ]\n\n", + ) + } else { + String::from( + "[ 🔁 ] [ 🔂 ] [ ⏮ ] [ ◀ ] [ ⏸ ] [ ▶ ] [ ⏭ ] [ ⏬ ] [ 🔀 ]\n\n", + ) + }; + + let mut control_indicators = String::new(); + + if self.autoplay_state.repeat_list { + control_indicators += " 🔁 "; } - None => String::from("Idle"), - }; - - let status_block = Block::default() - .title(status_title) - .borders(Borders::ALL) - .style(main_style); - let status_sizes = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(status_size.height / 10), - Constraint::Min(0), - ]) - .margin(1) - .split(status_size)[0]; - - let volume_size = subsize(status_sizes, 0); - let progress_size = subsize(status_sizes, 1); - let control_size = subsize(status_sizes, 2); - - let block = Block::default().title("Volume").borders(Borders::ALL); - let volume_gauge = Gauge::default() - .block(block) - .style(match cursor_state { - CursorState::Volume => focused_style, - _ => main_style, - }) - .gauge_style(main_style.fg(Color::Blue)) - .ratio(play.volume()); - - let block = Block::default().borders(Borders::ALL); - let progress_gauge = Gauge::default() - .block(block) - .style(main_style) - .gauge_style(main_style.fg(Color::Blue)) - .ratio(current_progress(&play)); - - let control_buttons = if is_paused(&play) { - String::from( - "[ 🔁 ] [ 🔂 ] [ ⏮ ] [ ◀ ] [ ▶ ] [ ▶ ] [ ⏭ ] [ ⏬ ] [ 🔀 ]\n\n", - ) - } else { - String::from( - "[ 🔁 ] [ 🔂 ] [ ⏮ ] [ ◀ ] [ ⏸ ] [ ▶ ] [ ⏭ ] [ ⏬ ] [ 🔀 ]\n\n", - ) - }; - - let mut control_indicators = String::new(); - - if autoplay_state.repeat_list { - control_indicators += " 🔁 "; - } - if autoplay_state.repeat { - control_indicators += " 🔂 "; - } - if autoplay_state.sequential { - control_indicators += " ⏬ "; - } - if autoplay_state.shuffle { - control_indicators += " 🔀 "; - } - - let block = Block::default().borders(Borders::ALL); - let control_paragraph = Paragraph::new(control_buttons + &control_indicators) - .block(block) - .alignment(Alignment::Center) - .style(match cursor_state { - CursorState::Control => focused_style, - _ => main_style, - }); - - f.render_stateful_widget(listing, listing_size, &mut list_state); - f.render_widget(status_block, status_size); - f.render_widget(volume_gauge, volume_size); - f.render_widget(progress_gauge, progress_size); - f.render_widget(control_paragraph, control_size); - })?; - - if current_progress(&play) == 1.0 { - if autoplay_state.repeat { - play.play(); - } else if autoplay_state.sequential { - let mut track = files - .iter() - .enumerate() - .find(|(_, file)| format!("file://{}", file.display()) == play.uri().unwrap()) - .unwrap() - .0 - + 1; - - if track >= files.len() && autoplay_state.repeat_list { - track = 0 + if self.autoplay_state.repeat { + control_indicators += " 🔂 "; } - - if track < files.len() { - play_path(&play, files[track].display(), args.volume); + if self.autoplay_state.sequential { + control_indicators += " ⏬ "; + } + if self.autoplay_state.shuffle { + control_indicators += " 🔀 "; } - } else if autoplay_state.shuffle { - let track = rand::random::<usize>() & files.len(); - play_path(&play, files[track].display(), args.volume); - } else if args.no_remain { - break; - } - } - if !event::poll(Duration::from_secs(1))? { - continue; - } + let block = Block::default().borders(Borders::ALL); + let control_paragraph = Paragraph::new(control_buttons + &control_indicators) + .block(block) + .alignment(Alignment::Center) + .style(match self.cursor_state { + CursorState::Control => focused_style, + _ => main_style, + }); + + f.render_stateful_widget(listing, listing_size, &mut self.list_state); + f.render_widget(status_block, status_size); + f.render_widget(volume_gauge, volume_size); + f.render_widget(progress_gauge, progress_size); + f.render_widget(control_paragraph, control_size); + })?; + + if self.current_progress() == 1.0 { + if self.autoplay_state.repeat { + self.play.play(); + } else if self.autoplay_state.sequential { + let mut track = self + .files + .iter() + .enumerate() + .find(|(_, file)| { + format!("file://{}", file.display()) == self.play.uri().unwrap() + }) + .unwrap() + .0 + + 1; + + if track >= self.files.len() && self.autoplay_state.repeat_list { + track = 0 + } - if let Event::Key(key) = event::read()? { - match key.code { - KeyCode::Esc | KeyCode::Char('q') => { - break; - } - KeyCode::Tab => { - cursor_state.overflowing_next(); - } - KeyCode::Char(' ') => { - if is_paused(&play) { - play.play(); - } else { - play.pause(); + if track < self.files.len() { + self.play_path(self.files[track].display()); } + } else if self.autoplay_state.shuffle { + let track = rand::random::<usize>() & self.files.len(); + self.play_path(self.files[track].display()); + } else if self.args.no_remain { + break; } - _ => match cursor_state { - CursorState::MusicList => match key.code { - KeyCode::Down => match list_state.selected() { - Some(i) => list_state.select(Some((i + 1) % files.len())), - None => list_state.select(Some(0)), - }, - KeyCode::Up => match list_state.selected() { - Some(i) => list_state.select(Some(if i > 0 { - (i - 1) % files.len() - } else { - files.len() - 1 - })), - None => list_state.select(Some(files.len() - 1)), - }, - KeyCode::Char('r') => { - let track = rand::random::<usize>() % files.len(); - list_state.select(Some(track)); - } - KeyCode::Char('R') => { - let track = rand::random::<usize>() % files.len(); - list_state.select(Some(track)); + } - play_path(&play, files[track].display(), args.volume); - } - KeyCode::Enter => { - let track = match list_state.selected() { - Some(i) => i, - None => { - continue; - } - }; + if !event::poll(Duration::from_secs(1))? { + continue; + } - play_path(&play, files[track].display(), args.volume); + if let Event::Key(key) = event::read()? { + match key.code { + KeyCode::Esc | KeyCode::Char('q') => { + break; + } + KeyCode::Tab => { + self.cursor_state.overflowing_next(); + } + KeyCode::Char(' ') => { + if self.is_paused() { + self.play.play(); + } else { + self.play.pause(); } - _ => {} - }, - CursorState::Volume => match key.code { - KeyCode::Left => play.set_volume(0.0_f64.max(play.volume() - 0.01)), - KeyCode::Right => play.set_volume(1.0_f64.min(play.volume() + 0.01)), - KeyCode::Home => play.set_volume(0.0), - KeyCode::End => play.set_volume(1.0), - KeyCode::Down => play.set_volume(0.0_f64.max(play.volume() - 0.05)), - KeyCode::Up => play.set_volume(1.0_f64.min(play.volume() + 0.05)), - _ => {} - }, - CursorState::Control => match key.code { - KeyCode::Left => { - if let Some(position) = play.position() { - play.seek(ClockTime::from_seconds( - 0_u64.max(position.seconds().saturating_sub(1)), - )); + } + _ => match self.cursor_state { + CursorState::MusicList => match key.code { + KeyCode::Down => match self.list_state.selected() { + Some(i) => self.list_state.select(Some((i + 1) % self.files.len())), + None => self.list_state.select(Some(0)), + }, + KeyCode::Up => match self.list_state.selected() { + Some(i) => self.list_state.select(Some(if i > 0 { + (i - 1) % self.files.len() + } else { + self.files.len() - 1 + })), + None => self.list_state.select(Some(self.files.len() - 1)), + }, + KeyCode::Char('r') => { + let track = rand::random::<usize>() % self.files.len(); + self.list_state.select(Some(track)); } - } - KeyCode::Right => { - if let Some(position) = play.position() { - if let Some(duration) = play.duration() { - play.seek(ClockTime::from_seconds( - duration - .seconds() - .min(position.seconds().saturating_add(1)), + KeyCode::Char('R') => { + let track = rand::random::<usize>() % self.files.len(); + self.list_state.select(Some(track)); + + self.play_path(self.files[track].display()); + } + KeyCode::Enter => { + let track = match self.list_state.selected() { + Some(i) => i, + None => { + continue; + } + }; + + self.play_path(self.files[track].display()); + } + _ => {} + }, + CursorState::Volume => match key.code { + KeyCode::Left => { + self.play.set_volume(0.0_f64.max(self.play.volume() - 0.01)) + } + KeyCode::Right => { + self.play.set_volume(1.0_f64.min(self.play.volume() + 0.01)) + } + KeyCode::Home => self.play.set_volume(0.0), + KeyCode::End => self.play.set_volume(1.0), + KeyCode::Down => { + self.play.set_volume(0.0_f64.max(self.play.volume() - 0.05)) + } + KeyCode::Up => { + self.play.set_volume(1.0_f64.min(self.play.volume() + 0.05)) + } + _ => {} + }, + CursorState::Control => match key.code { + KeyCode::Left => { + if let Some(position) = self.play.position() { + self.play.seek(ClockTime::from_seconds( + 0_u64.max(position.seconds().saturating_sub(1)), )); } } - } - KeyCode::Down => { - if let Some(position) = play.position() { - play.seek(ClockTime::from_seconds( - 0_u64.max(position.seconds().saturating_sub(15)), - )); + KeyCode::Right => { + if let Some(position) = self.play.position() { + if let Some(duration) = self.play.duration() { + self.play.seek(ClockTime::from_seconds( + duration + .seconds() + .min(position.seconds().saturating_add(1)), + )); + } + } } - } - KeyCode::Up => { - if let Some(position) = play.position() { - if let Some(duration) = play.duration() { - play.seek(ClockTime::from_seconds( - duration - .seconds() - .min(position.seconds().saturating_add(15)), + KeyCode::Down => { + if let Some(position) = self.play.position() { + self.play.seek(ClockTime::from_seconds( + 0_u64.max(position.seconds().saturating_sub(15)), )); } } - } - KeyCode::Char('r') => { - autoplay_state.repeat = !autoplay_state.repeat; - } - KeyCode::Char('s') => { - autoplay_state.shuffle = !autoplay_state.shuffle; - } - KeyCode::Char('l') => { - autoplay_state.sequential = !autoplay_state.sequential; - } - KeyCode::Char('i') => { - autoplay_state.repeat_list = !autoplay_state.repeat_list; - } - _ => {} + KeyCode::Up => { + if let Some(position) = self.play.position() { + if let Some(duration) = self.play.duration() { + self.play.seek(ClockTime::from_seconds( + duration + .seconds() + .min(position.seconds().saturating_add(15)), + )); + } + } + } + KeyCode::Char('r') => { + self.autoplay_state.repeat = !self.autoplay_state.repeat; + } + KeyCode::Char('s') => { + self.autoplay_state.shuffle = !self.autoplay_state.shuffle; + } + KeyCode::Char('l') => { + self.autoplay_state.sequential = !self.autoplay_state.sequential; + } + KeyCode::Char('i') => { + self.autoplay_state.repeat_list = !self.autoplay_state.repeat_list; + } + _ => {} + }, }, - }, + } } } + + disable_raw_mode()?; + terminal.clear()?; + terminal.set_cursor(0, 0)?; + + Ok(()) } +} + +fn subsize(area: Rect, i: u16) -> Rect { + let mut new_area = area; + new_area.y += i * area.height; - disable_raw_mode()?; - terminal.clear()?; - terminal.set_cursor(0, 0)?; + new_area +} + +fn main() -> anyhow::Result<()> { + gstreamer::init()?; + Instance::new()?.run()?; Ok(()) } |