alot of edge cases removed, ui improvments

This commit is contained in:
krolxon 2024-04-26 14:32:58 +05:30
parent 59e8e8cbe6
commit 04e5d2ad28
13 changed files with 320 additions and 112 deletions

7
Cargo.lock generated
View File

@ -225,6 +225,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "2.0.5" version = "2.0.5"
@ -386,6 +392,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"crossterm", "crossterm",
"humantime",
"mpd", "mpd",
"ratatui", "ratatui",
"rust-fuzzy-search", "rust-fuzzy-search",

View File

@ -13,3 +13,4 @@ simple-dmenu = "0.1.0"
ratatui = "0.26.2" ratatui = "0.26.2"
crossterm = "0.27.0" crossterm = "0.27.0"
rust-fuzzy-search = "0.1.1" rust-fuzzy-search = "0.1.1"
humantime = "2.1.0"

View File

@ -1,7 +1,7 @@
A MPD client in Rust A MPD client in Rust
### Keys ### Keys
- `q` OR 'Ctr+C' to quit - `q` OR `Ctr+C` to quit
- `p` to toggle pause - `p` to toggle pause
- `+` to increase volume - `+` to increase volume
- `-` to decrease volume - `-` to decrease volume
@ -16,9 +16,18 @@ A MPD client in Rust
- `3` to go to playlists view - `3` to go to playlists view
- `Enter` to add song/playlist to current playlist - `Enter` to add song/playlist to current playlist
- `a` to append the song to current playing queue - `a` to append the song to current playing queue
- `f` to go forward - `f` to go forwards
- `b` to go backwords - `b` to go backwards
- `>` to play next song from queue - `>` to play next song from queue
- `<` to play previous song from queue - `<` to play previous song from queue
- `U` to update the MPD database - `U` to update the MPD database
- `r` to toggle repeat
- `z` to toggle random
### TODO
- [x] fix performance issues
- [ ] improvements on queue control
- [ ] add to playlists, playlists view
- [ ] search for songs
- [ ] metadata based tree view
- [ ] Humantime format

View File

@ -14,7 +14,6 @@ pub struct App {
/// check if app is running /// check if app is running
pub running: bool, pub running: bool,
pub conn: Connection, pub conn: Connection,
pub song_list: ContentList<String>,
pub queue_list: ContentList<String>, pub queue_list: ContentList<String>,
pub pl_list: ContentList<String>, pub pl_list: ContentList<String>,
pub selected_tab: SelectedTab, pub selected_tab: SelectedTab,
@ -28,16 +27,6 @@ pub enum SelectedTab {
Playlists, Playlists,
} }
impl SelectedTab {
fn as_usize(&self) {
match self {
SelectedTab::Queue => 0,
SelectedTab::Playlists => 1,
SelectedTab::DirectoryBrowser => 2,
};
}
}
impl App { impl App {
pub fn builder(addrs: &str) -> AppResult<Self> { pub fn builder(addrs: &str) -> AppResult<Self> {
let mut conn = Connection::new(addrs).unwrap(); let mut conn = Connection::new(addrs).unwrap();
@ -47,14 +36,10 @@ impl App {
Self::get_queue(&mut conn, &mut queue_list.list); Self::get_queue(&mut conn, &mut queue_list.list);
let mut song_list = ContentList::new();
song_list.list = conn.songs_filenames.clone();
let browser = FileBrowser::new(); let browser = FileBrowser::new();
Ok(Self { Ok(Self {
running: true, running: true,
conn, conn,
song_list,
queue_list, queue_list,
pl_list, pl_list,
selected_tab: SelectedTab::DirectoryBrowser, selected_tab: SelectedTab::DirectoryBrowser,
@ -63,11 +48,9 @@ impl App {
} }
pub fn tick(&mut self) { pub fn tick(&mut self) {
self.conn.update_state(); self.conn.update_status();
self.conn.update_progress();
self.conn.update_volume();
self.update_queue(); self.update_queue();
self.browser.update_directory(&mut self.conn).unwrap(); // self.browser.update_directory(&mut self.conn).unwrap();
} }
pub fn quit(&mut self) { pub fn quit(&mut self) {

View File

@ -1,3 +1,5 @@
use mpd::Query;
use crate::{app::AppResult, connection::Connection}; use crate::{app::AppResult, connection::Connection};
#[derive(Debug)] #[derive(Debug)]
@ -59,32 +61,45 @@ impl FileBrowser {
if t == "directory" { if t == "directory" {
if path != "." { if path != "." {
self.prev_path = self.path.clone(); self.prev_path = self.path.clone();
self.path = path.to_string(); self.path = self.prev_path.clone() + "/" + path;
self.update_directory(conn)?; self.update_directory(conn)?;
self.prev_selected = self.selected; self.prev_selected = self.selected;
self.selected = 0; self.selected = 0;
// println!("self.path: {}", self.path);
// println!("self.prev_pat: {}", self.prev_path);
} }
} else { } else {
let list = conn // let list = conn
.songs_filenames // .songs_filenames
.iter() // .iter()
.map(|f| f.as_str()) // .map(|f| f.as_str())
.collect::<Vec<&str>>(); // .collect::<Vec<&str>>();
let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&path, &list) // let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&path, &list)
.get(0) // .get(0)
.unwrap() // .unwrap()
.clone(); // .clone();
let song = conn.get_song_with_only_filename(filename); for filename in conn.songs_filenames.clone().iter() {
conn.push(&song)?; if filename.contains(path) {
let song = conn.get_song_with_only_filename(filename);
conn.push(&song)?;
}
}
} }
Ok(()) Ok(())
} }
pub fn handle_go_back(&mut self, conn: &mut Connection) -> AppResult<()> { pub fn handle_go_back(&mut self, conn: &mut Connection) -> AppResult<()> {
self.path = self.prev_path.clone(); if self.prev_path != "." {
let r = self.path.rfind("/").unwrap();
self.path = self.path.as_str()[..r].to_string();
self.update_directory(conn)?;
} else {
self.path = self.prev_path.clone();
self.update_directory(conn)?;
}
self.selected = self.prev_selected; self.selected = self.prev_selected;
self.update_directory(conn)?;
Ok(()) Ok(())
} }
} }

View File

@ -6,10 +6,10 @@ use clap::{Parser, Subcommand};
pub struct Args { pub struct Args {
/// No TUI /// No TUI
#[arg(short= 'n', default_value="false")] #[arg(short= 'n', default_value="false")]
pub no_tui: bool, pub tui: bool,
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub command: Option<Command>,
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]

View File

@ -15,6 +15,8 @@ pub struct Connection {
pub elapsed: Duration, pub elapsed: Duration,
pub total_duration: Duration, pub total_duration: Duration,
pub volume: u8, pub volume: u8,
pub repeat: bool,
pub random: bool,
} }
impl Connection { impl Connection {
@ -27,8 +29,11 @@ impl Connection {
.into_iter() .into_iter()
.map(|x| x.file) .map(|x| x.file)
.collect(); .collect();
let (elapsed, total) = conn.status().unwrap().time.unwrap_or_default(); let status = conn.status().unwrap();
let volume: u8 = conn.status().unwrap_or_default().volume as u8; let (elapsed, total) = status.time.unwrap_or_default();
let volume: u8 = status.volume as u8;
let repeat = status.repeat;
let random = status.random;
Ok(Self { Ok(Self {
conn, conn,
@ -37,6 +42,8 @@ impl Connection {
elapsed, elapsed,
total_duration: total, total_duration: total,
volume, volume,
repeat,
random,
}) })
} }
@ -64,25 +71,33 @@ impl Connection {
Ok(()) Ok(())
} }
pub fn update_state(&mut self) -> String { /// Update status
match self.conn.status().unwrap().state { pub fn update_status(&mut self) {
let status = self.conn.status().unwrap();
// Playback State
match status.state {
State::Stop => self.state = "Stopped".to_string(), State::Stop => self.state = "Stopped".to_string(),
State::Play => self.state = "Playing".to_string(), State::Play => self.state = "Playing".to_string(),
State::Pause => self.state = "Paused".to_string(), State::Pause => self.state = "Paused".to_string(),
} }
self.state.clone()
}
pub fn update_progress(&mut self) { // Progress
let (elapsed, total) = self.conn.status().unwrap().time.unwrap_or_default(); let (elapsed, total) = status.time.unwrap_or_default();
self.elapsed = elapsed; self.elapsed = elapsed;
self.total_duration = total; self.total_duration = total;
// Volume
self.volume = status.volume as u8;
// Repeat mode
self.repeat = status.repeat;
// Random mode
self.random = status.random;
} }
pub fn update_volume(&mut self) { /// Get progress ratio of current playing song
self.volume = self.conn.status().unwrap_or_default().volume as u8;
}
pub fn get_progress_ratio(&self) -> f64 { pub fn get_progress_ratio(&self) -> f64 {
let total = self.total_duration.as_secs_f64(); let total = self.total_duration.as_secs_f64();
if total == 0.0 { if total == 0.0 {
@ -143,11 +158,6 @@ impl Connection {
} }
} }
/// get current playing song
pub fn get_current_song(&mut self) -> Option<String> {
self.conn.currentsong().unwrap().unwrap_or_default().title
}
/// Print status to stdout /// Print status to stdout
pub fn status(&mut self) { pub fn status(&mut self) {
let current_song = self.conn.currentsong(); let current_song = self.conn.currentsong();
@ -170,7 +180,7 @@ impl Connection {
let song = self.conn.currentsong()?.unwrap_or_default(); let song = self.conn.currentsong()?.unwrap_or_default();
if let Some(s) = song.title { if let Some(s) = song.title {
if let Some(a) = song.artist { if let Some(a) = song.artist {
return Ok(Some(format!("\"{}\" By {}", a, s))); return Ok(Some(format!("\"{}\" By {}", s, a)));
} else { } else {
return Ok(Some(s)); return Ok(Some(s));
} }
@ -190,7 +200,26 @@ impl Connection {
self.conn.toggle_pause().unwrap(); self.conn.toggle_pause().unwrap();
} }
/// Toggle Repeat mode
pub fn toggle_repeat(&mut self) {
if self.conn.status().unwrap().repeat {
self.conn.repeat(false).unwrap();
} else {
self.conn.repeat(true).unwrap();
}
}
/// Toggle random mode
pub fn toggle_random(&mut self) {
if self.conn.status().unwrap().random {
self.conn.random(false).unwrap();
} else {
self.conn.random(true).unwrap();
}
}
// Volume controls // Volume controls
/// Increase Volume
pub fn inc_volume(&mut self, v: i8) { pub fn inc_volume(&mut self, v: i8) {
let cur = self.conn.status().unwrap().volume; let cur = self.conn.status().unwrap().volume;
if cur + v <= 100 { if cur + v <= 100 {
@ -198,6 +227,7 @@ impl Connection {
} }
} }
/// Decrease volume
pub fn dec_volume(&mut self, v: i8) { pub fn dec_volume(&mut self, v: i8) {
let cur = self.conn.status().unwrap().volume; let cur = self.conn.status().unwrap().volume;
if cur - v >= 0 { if cur - v >= 0 {

View File

@ -13,6 +13,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
app.quit(); app.quit();
} else { } else {
app.conn.conn.clear()?; app.conn.conn.clear()?;
app.conn.update_status();
} }
} }
@ -47,6 +48,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
.push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?; .push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?;
} }
} }
app.conn.update_status();
} }
KeyCode::Char('h') => match app.selected_tab { KeyCode::Char('h') => match app.selected_tab {
@ -68,6 +70,18 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
app.conn.pause(); app.conn.pause();
} }
// Toggle rpeat
KeyCode::Char('r') => {
app.conn.toggle_repeat();
app.conn.update_status();
}
// Toggle random
KeyCode::Char('z') => {
app.conn.toggle_random();
app.conn.update_status();
}
// Dmenu prompt // Dmenu prompt
KeyCode::Char('D') => { KeyCode::Char('D') => {
app.conn.play_dmenu()?; app.conn.play_dmenu()?;
@ -75,9 +89,23 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
// add to queue // add to queue
KeyCode::Char('a') => { KeyCode::Char('a') => {
let song = app.conn.get_song_with_only_filename( // let song = app.conn.get_song_with_only_filename(
app.conn.songs_filenames.get(app.song_list.index).unwrap(), // app.conn.songs_filenames.get(app.song_list.index).unwrap(),
); // );
let list = app
.conn
.songs_filenames
.iter()
.map(|f| f.as_str())
.collect::<Vec<&str>>();
let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&app.browser.path, &list)
.get(0)
.unwrap()
.clone();
let song = app.conn.get_song_with_only_filename(filename);
app.conn.conn.push(&song)?; app.conn.conn.push(&song)?;
} }
@ -127,16 +155,22 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
// Volume controls // Volume controls
KeyCode::Char('=') => { KeyCode::Char('=') => {
app.conn.inc_volume(2); app.conn.inc_volume(2);
app.conn.update_volume(); app.conn.update_status();
} }
KeyCode::Char('-') => { KeyCode::Char('-') => {
app.conn.dec_volume(2); app.conn.dec_volume(2);
app.conn.update_volume(); app.conn.update_status();
} }
// Delete highlighted song from the queue // Delete highlighted song from the queue
KeyCode::Char('d') => { KeyCode::Char('d') => {
if app.queue_list.index >= app.queue_list.list.len() - 1 {
if app.queue_list.index != 0 {
app.queue_list.index -= 1;
}
}
app.conn.conn.delete(app.queue_list.index as u32)?; app.conn.conn.delete(app.queue_list.index as u32)?;
app.update_queue(); app.update_queue();
} }

View File

@ -13,6 +13,9 @@ pub mod tui;
/// Content list /// Content list
pub mod list; pub mod list;
/// Song
pub mod song;
/// File Browser /// File Browser
pub mod browser; pub mod browser;

View File

@ -18,18 +18,24 @@ impl<T> ContentList<T> {
// self.index += 1; // self.index += 1;
// } // }
if self.index == self.list.len() - 1 { let len = self.list.len();
self.index = 0; if len != 0 {
} else { if self.index == self.list.len() - 1 {
self.index += 1; self.index = 0;
} else {
self.index += 1;
}
} }
} }
/// Go to previous item in list /// Go to previous item in list
pub fn prev(&mut self) { pub fn prev(&mut self) {
if self.index == 0 { if self.index == 0 {
self.index = self.list.len() - 1; let len = self.list.len();
} else { if len != 0 {
self.index = len - 1;
}
} else {
self.index -= 1; self.index -= 1;
} }
} }

View File

@ -9,6 +9,7 @@ use rmptui::connection::Connection;
use rmptui::event::Event; use rmptui::event::Event;
use rmptui::event::EventHandler; use rmptui::event::EventHandler;
use rmptui::handler; use rmptui::handler;
use rmptui::song::RSong;
use rmptui::tui; use rmptui::tui;
use std::io; use std::io;
@ -23,41 +24,52 @@ pub type Result<T> = core::result::Result<T, Error>;
pub type Error = Box<dyn std::error::Error>; pub type Error = Box<dyn std::error::Error>;
fn main() -> AppResult<()> { fn main() -> AppResult<()> {
// let args = Args::parse(); let args = Args::parse();
// if args.no_tui {
// handle_tui()?;
// } else {
// match args.command {
// Command::Volume { vol } => {
// conn.set_volume(vol);
// }
// Command::Dmenu => conn.play_dmenu().unwrap(),
// Command::Fzf => conn.play_fzf().unwrap(),
// Command::Status => conn.status(),
// Command::Pause => conn.pause(),
// Command::Toggle => conn.toggle_pause(),
// };
// }
let backend = CrosstermBackend::new(io::stderr());
let terminal = Terminal::new(backend)?;
let mut app = App::builder("127.0.0.1:6600")?; let mut app = App::builder("127.0.0.1:6600")?;
let events = EventHandler::new(250);
if !args.tui {
handle_tui(&mut app)?;
} else {
match args.command {
Some(Command::Dmenu) => app.conn.play_dmenu()?,
Some(Command::Fzf) => app.conn.play_fzf().unwrap(),
Some(Command::Status) => app.conn.status(),
Some(Command::Pause) => app.conn.pause(),
Some(Command::Toggle) => app.conn.toggle_pause(),
_ => {
let mut vec: Vec<RSong> = Vec::new();
for filename in app.conn.songs_filenames {
let song = RSong::new(&mut app.conn.conn, filename);
vec.push(song);
}
println!("{:#?}", vec);
}
}
}
Ok(())
}
pub fn handle_tui(app: &mut App) -> AppResult<()> {
let backend = CrosstermBackend::new(io::stderr());
let terminal = Terminal::new(backend)?;
let events = EventHandler::new(1000);
let mut tui = tui::Tui::new(terminal, events); let mut tui = tui::Tui::new(terminal, events);
tui.init()?; tui.init()?;
// update the directory
app.browser.update_directory(&mut app.conn).unwrap();
while app.running { while app.running {
tui.draw(&mut app)?; tui.draw(app)?;
match tui.events.next()? { match tui.events.next()? {
Event::Tick => app.tick(), Event::Tick => app.tick(),
Event::Key(key_event) => handler::handle_key_events(key_event, &mut app)?, Event::Key(key_event) => handler::handle_key_events(key_event, app)?,
Event::Mouse(_) => {} Event::Mouse(_) => {}
Event::Resize(_, _) => {} Event::Resize(_, _) => {}
} }
} }
Ok(()) Ok(())
} }

67
src/song.rs Executable file
View File

@ -0,0 +1,67 @@
use mpd::{Client, Song};
#[derive(Debug)]
#[derive(Clone )]
pub struct RSong {
pub file: String,
pub artist: Option<String>,
pub title: Option<String>,
pub duration: Option<u32>,
pub last_mod: Option<String>,
pub name: Option<String>,
pub place: Option<String>,
pub range: Option<String>,
pub tags: Vec<(String, String)>,
}
impl RSong {
pub fn new(c: &mut Client, filename: String) -> Self {
let mut s = RSong {
file: filename.clone(),
artist: None,
title: None,
duration: None,
last_mod: None,
name: None,
place: None,
range: None,
tags: vec![],
};
// Dummy song
let song = Song {
file: filename.clone(),
artist: None,
title: None,
duration: None,
last_mod: None,
name: None,
place: None,
range: None,
tags: vec![("".to_string(), "".to_string())],
};
for (k, v) in (c.readcomments(song).unwrap()).flatten() {
if k.to_lowercase().contains("artist") {
s.artist = Some(v);
} else if k.to_lowercase().contains("title") {
s.title = Some(v);
} else if k.to_lowercase().contains("duration") {
s.duration = Some(v.parse::<u32>().unwrap());
} else if k.to_lowercase().contains("lastmod") {
s.last_mod = Some(v);
} else if k.to_lowercase().contains("name") {
s.name = Some(v);
} else if k.to_lowercase().contains("place") {
s.place = Some(v);
} else if k.to_lowercase().contains("range") {
s.range = Some(v);
} else {
s.tags.push((k, v));
}
}
s
}
}

View File

@ -14,7 +14,7 @@ pub fn render(app: &mut App, frame: &mut Frame) {
// Layout // Layout
let layout = Layout::default() let layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints(vec![Constraint::Percentage(93), Constraint::Percentage(7)]) .constraints(vec![Constraint::Percentage(93), Constraint::Min(3)])
.split(frame.size()); .split(frame.size());
match app.selected_tab { match app.selected_tab {
@ -52,7 +52,12 @@ fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
) )
.borders(Borders::ALL), .borders(Borders::ALL),
) )
.highlight_style(Style::new().add_modifier(Modifier::REVERSED)) .highlight_style(
Style::new()
.fg(Color::Cyan)
.bg(Color::Black)
.add_modifier(Modifier::REVERSED),
)
.highlight_symbol(">>") .highlight_symbol(">>")
.repeat_highlight_symbol(true) .repeat_highlight_symbol(true)
.scroll_padding(20); .scroll_padding(20);
@ -72,7 +77,12 @@ fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
); );
let list = List::new(app.queue_list.list.clone()) let list = List::new(app.queue_list.list.clone())
.block(title.borders(Borders::ALL)) .block(title.borders(Borders::ALL))
.highlight_style(Style::new().add_modifier(Modifier::REVERSED)) .highlight_style(
Style::new()
.fg(Color::Cyan)
.bg(Color::Black)
.add_modifier(Modifier::REVERSED),
)
.highlight_symbol(">>") .highlight_symbol(">>")
.repeat_highlight_symbol(true); .repeat_highlight_symbol(true);
@ -93,7 +103,12 @@ fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
let list = List::new(app.pl_list.list.clone()) let list = List::new(app.pl_list.list.clone())
.block(title.borders(Borders::ALL)) .block(title.borders(Borders::ALL))
.highlight_style(Style::new().add_modifier(Modifier::REVERSED)) .highlight_style(
Style::new()
.fg(Color::Cyan)
.bg(Color::Black)
.add_modifier(Modifier::REVERSED),
)
.highlight_symbol(">>") .highlight_symbol(">>")
.repeat_highlight_symbol(true); .repeat_highlight_symbol(true);
@ -103,39 +118,65 @@ fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
/// Draws Progress Bar /// Draws Progress Bar
fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) { fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
// Get the current playing song
let song = app let song = app
.conn .conn
.now_playing() .now_playing()
.unwrap() .unwrap()
.unwrap_or_else(|| "No Title Found".to_string()); .unwrap_or_else(|| "No Title Found".to_string());
let state = &app.conn.state; // Get the current playing state
// let (elapsed, total) = app.conn.conn.status().unwrap().time.unwrap_or_default(); let mut state: String = String::new();
if !app.queue_list.list.is_empty() {
state = app.conn.state.clone();
state.push(':');
}
// Get the current modes
let mut modes_bottom: String = String::new();
// we do this to check if at least one mode is enabled so we can push "[]"
if app.conn.repeat | app.conn.random {
modes_bottom.push('r');
}
if !modes_bottom.is_empty() {
modes_bottom.clear();
modes_bottom.push('[');
if app.conn.repeat {
modes_bottom.push('r');
}
if app.conn.random {
modes_bottom.push('z');
}
modes_bottom.push(']');
};
// get the duration
let duration = if app.conn.total_duration.as_secs() != 0 {
format!(
"[{}/{}]",
humantime::format_duration(app.conn.elapsed),
humantime::format_duration(app.conn.total_duration)
)
} else {
"".to_string()
};
// Define the title
let title = Block::default() let title = Block::default()
.title(Title::from(format!("{}: ", state).red().bold())) .title(Title::from(format!("{}", state).red().bold()))
.title(Title::from(song.green().bold())) .title(Title::from(song.green().bold()))
.title( .title(Title::from(duration.cyan().bold()).alignment(Alignment::Right))
Title::from( .title(Title::from(format!("{}", modes_bottom)).position(block::Position::Bottom))
format!(
"{}/{}",
app.conn.elapsed.as_secs(),
app.conn.total_duration.as_secs()
)
.cyan()
.bold(),
)
.alignment(Alignment::Right),
)
.borders(Borders::ALL); .borders(Borders::ALL);
// .title(Title::from(app.conn.conn.status().unwrap_or_default().volume.to_string().yellow())).title_alignment(Alignment::Right);
let progress_bar = LineGauge::default() let progress_bar = LineGauge::default()
.block(title.borders(Borders::ALL)) .block(title.borders(Borders::ALL))
.gauge_style( .gauge_style(
Style::default() Style::default()
.fg(Color::LightBlue) .fg(Color::Blue)
.bg(Color::Gray) .bg(Color::Black)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
) )
.line_set(symbols::line::THICK) .line_set(symbols::line::THICK)