feature: tab indicator
implemented a tab indicator that will show up when the user changes tabs in grrs. after settling for 3 seconds, the indicator will go away, and things will snap back into place.
diff --git a/src/app.rs b/src/app.rs
@@ -21,6 +21,7 @@ pub const REFRESH_WINDOW: std::time::Duration = std::time::Duration::from_secs(3
pub enum AppMode {
Playing,
LikedSongs,
+ History,
ConfigError(Vec<String>),
}
@@ -66,6 +67,7 @@ pub struct AppState {
pub volume_time: Option<Instant>,
pub config_scroll: u16,
pub notified_song_id: i64,
+ pub nav_time: Option<Instant>,
}
impl AppState {
@@ -173,6 +175,7 @@ impl AppState {
volume_time: None,
config_scroll: 0,
notified_song_id: 0,
+ nav_time: None,
}
}
diff --git a/src/input.rs b/src/input.rs
@@ -131,6 +131,7 @@ pub async fn handle_input(
if state.mode == AppMode::Playing {
if key.code == KeyCode::Tab {
state.mode = AppMode::LikedSongs;
+ state.nav_time = Some(std::time::Instant::now());
let sorted = state.get_sorted_likes();
if !sorted.is_empty() {
state.liked_list_state.select(Some(0));
@@ -194,9 +195,14 @@ pub async fn handle_input(
state.status_message_time = Some(std::time::Instant::now());
}
} else if state.mode == AppMode::LikedSongs {
- if key.code == KeyCode::Tab || key.code == KeyCode::Esc {
+ if key.code == KeyCode::Tab {
+ state.mode = AppMode::History;
+ state.selected_art = None;
+ state.nav_time = Some(std::time::Instant::now());
+ } else if key.code == KeyCode::Esc {
state.mode = AppMode::Playing;
state.selected_art = None;
+ state.nav_time = Some(std::time::Instant::now());
} else if key.code == KeyCode::Up || key.code == KeyCode::Char('k') {
let sorted = state.get_sorted_likes();
if !sorted.is_empty() {
@@ -272,6 +278,11 @@ pub async fn handle_input(
state.status_message_time = Some(std::time::Instant::now());
}
}
+ } else if state.mode == AppMode::History {
+ if key.code == KeyCode::Tab || key.code == KeyCode::Esc {
+ state.mode = AppMode::Playing;
+ state.nav_time = Some(std::time::Instant::now());
+ }
}
},
CEvent::Mouse(mouse) => {
diff --git a/src/main.rs b/src/main.rs
@@ -493,6 +493,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
{
state.volume_time = None;
}
+
+ let mut clear_for_nav = false;
+ if let Some(t) = state.nav_time
+ && t.elapsed().as_secs() >= 3
+ {
+ state.nav_time = None;
+ clear_for_nav = true;
+ }
+
+ if clear_for_nav {
+ hide_art = true;
+ let _ = terminal.draw(|f| draw_ui(f, &mut state, hide_art));
+ hide_art = false;
+ }
},
}
}
diff --git a/src/ui.rs b/src/ui.rs
@@ -120,23 +120,70 @@ pub fn draw_ui(f: &mut Frame, state: &mut AppState, hide_art: bool) {
return;
}
- let main_chunks = Layout::default()
- .direction(Direction::Vertical)
- .margin(2)
- .constraints([Constraint::Min(10), Constraint::Length(1)])
- .split(f.area());
- let dynamic_width = (main_chunks[0].height * 2).clamp(10, 45);
+ let mut constraints = vec![];
+
+ if state.nav_time.is_some() {
+ constraints.push(Constraint::Length(1));
+ constraints.push(Constraint::Length(3));
+ }
+
+ constraints.push(Constraint::Min(10));
+ constraints.push(Constraint::Length(1));
+
+ let main_chunks =
+ Layout::default().direction(Direction::Vertical).margin(2).constraints(constraints).split(f.area());
+
+ let (content_chunk, bottom_chunk) = if state.nav_time.is_some() {
+ let nav_text = Line::from(vec![
+ Span::styled(
+ "[player]",
+ if state.mode == AppMode::Playing {
+ Style::default().fg(state.theme_color).bold()
+ } else {
+ Style::default().fg(state.secondary_color)
+ },
+ ),
+ Span::raw(" "),
+ Span::styled(
+ "[library]",
+ if state.mode == AppMode::LikedSongs {
+ Style::default().fg(state.theme_color).bold()
+ } else {
+ Style::default().fg(state.secondary_color)
+ },
+ ),
+ Span::raw(" "),
+ Span::styled(
+ "[history]",
+ if state.mode == AppMode::History {
+ Style::default().fg(state.theme_color).bold()
+ } else {
+ Style::default().fg(state.secondary_color)
+ },
+ ),
+ ]);
+ f.render_widget(Paragraph::new(nav_text).alignment(Alignment::Center), main_chunks[0]);
+
+ (main_chunks[2], main_chunks[3])
+ } else {
+ (main_chunks[0], main_chunks[1])
+ };
+
+ let dynamic_width = (content_chunk.height * 2).clamp(10, 45);
let content_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(dynamic_width), Constraint::Length(2), Constraint::Min(20)])
- .split(main_chunks[0]);
+ .split(content_chunk);
match state.mode {
AppMode::Playing => {
- draw_player(f, state, content_chunks.clone(), main_chunks[1], main_chunks[0].height, hide_art);
+ draw_player(f, state, content_chunks.clone(), bottom_chunk, content_chunk.height, hide_art);
},
AppMode::LikedSongs => {
- draw_library(f, state, content_chunks, main_chunks[1], hide_art);
+ draw_library(f, state, content_chunks, bottom_chunk, hide_art);
+ },
+ AppMode::History => {
+ draw_history(f, state, content_chunks, bottom_chunk);
},
_ => {},
}
@@ -329,6 +376,17 @@ fn draw_library(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom
f.render_widget(Paragraph::new(build_status_bar(state)).alignment(Alignment::Left), bottom);
}
+fn draw_history(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom: Rect) {
+ f.render_widget(Clear, content[0]);
+ f.render_widget(
+ Paragraph::new("History tab coming soon...")
+ .alignment(Alignment::Center)
+ .style(Style::default().fg(state.secondary_color)),
+ content[2],
+ );
+ f.render_widget(Paragraph::new(build_status_bar(state)).alignment(Alignment::Left), bottom);
+}
+
fn draw_help_popup(f: &mut Frame, state: &AppState) {
let area = centered_rect(44, 14, f.area());
f.render_widget(Clear, area);
@@ -361,7 +419,7 @@ fn draw_help_popup(f: &mut Frame, state: &AppState) {
0 => vec![
Row::new(vec![
Span::styled("[Tab]", Style::default().bold().fg(state.theme_color)),
- Span::raw("Switch library / player"),
+ Span::raw("Cycle player / library / history"),
]),
Row::new(vec![
Span::styled("[m]", Style::default().bold().fg(state.theme_color)),