mirror of
				https://github.com/talwat/lowfi
				synced 2025-10-31 11:18:49 +00:00 
			
		
		
		
	feat: make ui much more asthetically pleasing
This commit is contained in:
		
							parent
							
								
									e9857e5c11
								
							
						
					
					
						commit
						1b2466e1f8
					
				
							
								
								
									
										117
									
								
								src/player/ui.rs
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								src/player/ui.rs
									
									
									
									
									
								
							| @ -1,9 +1,11 @@ | ||||
| use std::{io::stderr, sync::Arc, time::Duration}; | ||||
| 
 | ||||
| use crate::tracks::TrackInfo; | ||||
| 
 | ||||
| use super::Player; | ||||
| use crossterm::{ | ||||
|     cursor::{MoveToColumn, MoveUp, Show}, | ||||
|     style::Print, | ||||
|     cursor::{Hide, MoveToColumn, MoveUp, Show}, | ||||
|     style::{Print, Stylize}, | ||||
|     terminal::{Clear, ClearType}, | ||||
| }; | ||||
| use tokio::{ | ||||
| @ -14,53 +16,109 @@ use tokio::{ | ||||
| 
 | ||||
| use super::Messages; | ||||
| 
 | ||||
| async fn interface(queue: Arc<Player>) -> eyre::Result<()> { | ||||
|     const WIDTH: usize = 25; | ||||
|     const PROGRESS_WIDTH: usize = WIDTH - 4; | ||||
| fn format_duration(duration: &Duration) -> String { | ||||
|     let seconds = duration.as_secs() % 60; | ||||
|     let minutes = duration.as_secs() / 60; | ||||
| 
 | ||||
|     loop { | ||||
|         // We can get away with only redrawing every 0.25 seconds
 | ||||
|         // since it's just an audio player.
 | ||||
|         sleep(Duration::from_secs_f32(0.25)).await; | ||||
|         crossterm::execute!(stderr(), Clear(ClearType::FromCursorDown))?; | ||||
|     format!("{:02}:{:02}", minutes, seconds) | ||||
| } | ||||
| 
 | ||||
|         let mut main = match queue.current.load().as_ref() { | ||||
|             Some(x) => { | ||||
|                 if queue.sink.is_paused() { | ||||
|                     format!("paused {}", x.format_name()) | ||||
|                 } else { | ||||
|                     format!("playing {}", x.format_name()) | ||||
|                 } | ||||
|             } | ||||
|             None => "loading...".to_owned(), | ||||
| enum Action { | ||||
|     Paused(TrackInfo), | ||||
|     Playing(TrackInfo), | ||||
|     Loading, | ||||
| } | ||||
| 
 | ||||
| impl Action { | ||||
|     fn format(&self) -> (String, usize) { | ||||
|         let (word, subject) = match self { | ||||
|             Action::Playing(x) => ("playing", Some(x.format_name())), | ||||
|             Action::Paused(x) => ("paused", Some(x.format_name())), | ||||
|             Action::Loading => ("loading", None), | ||||
|         }; | ||||
| 
 | ||||
|         main.push_str("\r\n"); | ||||
|         if let Some(subject) = subject { | ||||
|             ( | ||||
|                 format!("{} {}", word, subject.bold()), | ||||
|                 word.len() + 1 + subject.len(), | ||||
|             ) | ||||
|         } else { | ||||
|             (format!("{}", word), word.len()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| async fn interface(queue: Arc<Player>) -> eyre::Result<()> { | ||||
|     const WIDTH: usize = 27; | ||||
|     const PROGRESS_WIDTH: usize = WIDTH - 16; | ||||
| 
 | ||||
|     loop { | ||||
|         let (mut main, len) = match queue.current.load().as_ref() { | ||||
|             Some(x) => { | ||||
|                 if queue.sink.is_paused() { | ||||
|                     Action::Paused(*x.clone()) | ||||
|                 } else { | ||||
|                     Action::Playing(*x.clone()) | ||||
|                 } | ||||
|             } | ||||
|             None => Action::Loading, | ||||
|         } | ||||
|         .format(); | ||||
| 
 | ||||
|         if len > WIDTH { | ||||
|             main = format!("{}...", &main[..=WIDTH]); | ||||
|         } else { | ||||
|             main = format!("{}{}", main, " ".repeat(WIDTH - len)); | ||||
|         } | ||||
| 
 | ||||
|         let mut duration = Duration::new(0, 0); | ||||
|         let elapsed = queue.sink.get_pos(); | ||||
| 
 | ||||
|         let mut filled = 0; | ||||
|         if let Some(current) = queue.current.load().as_ref() { | ||||
|             if let Some(duration) = current.duration { | ||||
|                 let elapsed = queue.sink.get_pos().as_secs() as f32 / duration.as_secs() as f32; | ||||
|             if let Some(x) = current.duration { | ||||
|                 duration = x; | ||||
| 
 | ||||
|                 let elapsed = elapsed.as_secs() as f32 / duration.as_secs() as f32; | ||||
|                 filled = (elapsed * PROGRESS_WIDTH as f32).round() as usize; | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         let progress = format!( | ||||
|             " [{}{}] ", | ||||
|             " [{}{}] {}/{} ", | ||||
|             "/".repeat(filled as usize), | ||||
|             " ".repeat(PROGRESS_WIDTH - filled) | ||||
|             " ".repeat(PROGRESS_WIDTH - filled), | ||||
|             format_duration(&elapsed), | ||||
|             format_duration(&duration), | ||||
|         ); | ||||
|         let bar = ["[s]kip", "[p]ause", "[q]uit"]; | ||||
|         let bar = [ | ||||
|             format!("{}kip", "[s]".bold()), | ||||
|             format!("{}ause", "[p]".bold()), | ||||
|             format!("{}uit", "[q]".bold()), | ||||
|         ]; | ||||
| 
 | ||||
|         crossterm::execute!(stderr(), MoveToColumn(0), Print(main))?; | ||||
|         crossterm::execute!(stderr(), Print(progress), Print("\r\n"))?; | ||||
|         crossterm::execute!(stderr(), Print(bar.join("   ")), Print("\r\n"))?; | ||||
|         crossterm::execute!(stderr(), MoveToColumn(0), MoveUp(3))?; | ||||
|         // Formats the menu properly
 | ||||
|         let menu = | ||||
|             [main, progress, bar.join("    ")].map(|x| format!("│ {x} │\r\n").reset().to_string()); | ||||
| 
 | ||||
|         crossterm::execute!(stderr(), Clear(ClearType::FromCursorDown))?; | ||||
|         crossterm::execute!( | ||||
|             stderr(), | ||||
|             MoveToColumn(0), | ||||
|             Print(format!("┌{}┐\r\n", "─".repeat(WIDTH + 2))), | ||||
|             Print(menu.join("")), | ||||
|             Print(format!("└{}┘", "─".repeat(WIDTH + 2))), | ||||
|             MoveToColumn(0), | ||||
|             MoveUp(4) | ||||
|         )?; | ||||
| 
 | ||||
|         sleep(Duration::from_secs_f32(0.25)).await; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub async fn start(queue: Arc<Player>, sender: Sender<Messages>) -> eyre::Result<()> { | ||||
|     crossterm::terminal::enable_raw_mode()?; | ||||
|     crossterm::execute!(stderr(), Hide)?; | ||||
|     //crossterm::execute!(stderr(), EnterAlternateScreen, MoveTo(0, 0))?;
 | ||||
| 
 | ||||
|     task::spawn(interface(queue.clone())); | ||||
| @ -90,6 +148,7 @@ pub async fn start(queue: Arc<Player>, sender: Sender<Messages>) -> eyre::Result | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //crossterm::execute!(stderr(), LeaveAlternateScreen)?;
 | ||||
|     crossterm::execute!(stderr(), Clear(ClearType::FromCursorDown), Show)?; | ||||
|     crossterm::terminal::disable_raw_mode()?; | ||||
| 
 | ||||
|  | ||||
| @ -34,7 +34,12 @@ pub struct TrackInfo { | ||||
| 
 | ||||
| impl TrackInfo { | ||||
|     pub fn format_name(&self) -> &'static str { | ||||
|         self.name.split("/").nth(2).unwrap() | ||||
|         self.name | ||||
|             .split("/") | ||||
|             .nth(2) | ||||
|             .unwrap() | ||||
|             .strip_suffix(".mp3") | ||||
|             .unwrap() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user