aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHimbeerserverDE <himbeerserverde@gmail.com>2023-01-02 18:39:43 +0100
committerHimbeerserverDE <himbeerserverde@gmail.com>2023-01-02 18:39:43 +0100
commitee94b47c80a6d580119104b1b3ce16491c090a73 (patch)
tree2ab160f2e237a91e65cc19cef8fb41ad05ff779c
parent2df46f83518d1e8d1603b71ee6bb4962cd4517d7 (diff)
instance-oriented refactoring
-rw-r--r--src/main.rs630
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(())
}