diff --git a/README.md b/README.md
index bbd18cd..86a7c70 100644
--- a/README.md
+++ b/README.md
@@ -23,9 +23,12 @@
- **Click to Play**: Select artists, albums, and tracks directly with mouse clicks or double-click.
- **Scroll Wheel**: Scroll library panes or adjust volume seamlessly.
- **Interactive Controls**: Clickable play/pause, next/prev, shuffle, repeat, star, volume, and header buttons.
+- 🔁 **Smart Queue & Playback Modes**:
+- 📻 **Auto-DJ / Library Radio Mode**: Press a or click `[📻 DJ]` to start continuous, infinite playback of random tracks across your whole server library.
+- 🖼️ **Terminal Cover Art**: Automatic album cover thumbnail downloading and TrueColor ANSI Half-block pixel art rendering in the dedicated sidebar pane with retro vinyl fallback animation!
- 🔁 **Smart Queue & Playback Modes**:
- Auto-advance to the next song when a track ends.
- - **Shuffle** mode (`🔀`) and **Repeat** modes (`🔁 Off`, `🔁 All`, `🔂 One`).
+ - **Shuffle** mode (`🔀`), **Repeat** modes (`🔁 Off`, `🔁 All`, `🔂 One`), and **Auto-DJ** (`📻 DJ`).
- Dedicated **Queue Viewer** (u) and **Lyrics Modal** (y).
- ❤️ **Favorites & Scrobbling**:
- Star / Like tracks (f / `❤️`) synced with your Navidrome server.
@@ -36,7 +39,6 @@
- **Nord**
- **Gruvbox Dark**
- *(Press t to cycle themes instantly!)*
-- 🖼️ **Terminal Cover Art**: Unicode Half-block pixel art thumbnail rendering.
- ⚙️ **Interactive Setup & Settings**: Built-in 2-column configuration wizard on first launch with persistent TOML config.
- 🔍 **Instant Global Search**: Search artists, albums, and songs across your entire music library (/).
@@ -51,13 +53,14 @@
│ 🎤 Gorillaz (8) ││ 💿 Random Access Memories (2013) [13 tracks] │
│ 🎤 Radiohead (9) ││ 💿 Alive 2007 (2007) [12 tracks] │
│ 🎤 Tame Impala (4) │└────────────────────────────────────────────────────────────────────────────────┘
-│ 🎤 The Weeknd (5) │┌─ Tracks [14] ──────────────────────────────────────────────────────────────────┐
-│ 🎤 Pink Floyd (11) ││ 🔊 ❤️ One More Time [05:20] │
-│ 🎤 Queen (15) ││ 02. Aerodynamic [03:27] │
-│ 🎤 Arctic Monkeys (6) ││ 03. Digital Love [04:58] │
+├─ 🖼️ Now Playing & Cover ──┤┌─ Tracks [14] ──────────────────────────────────────────────────────────────────┐
+│ ▄██████████████▄ ││ 🔊 ❤️ One More Time [05:20] │
+│ ████████████████ ││ 02. Aerodynamic [03:27] │
+│ ▀██████████████▀ ││ 03. Digital Love [04:58] │
+│ Daft Punk - Discovery ││ 04. Harder, Better, Faster, Stronger [03:45] │
└────────────────────────────┘└────────────────────────────────────────────────────────────────────────────────┘
┌─ 🎵 rhyth-rs v0.1.0-beta ────────────────────────────────────────────────────────────────────────────────────┐
-│ ❤️ One More Time • Daft Punk ⏮ ⏸ ⏭ │ 🔀 🔁 │ ▂▃▅▆▇▆▄▃▂▂▃▄▅ [y] Lyrics [u] Queue 🔊 85% │
+│ ❤️ One More Time • Daft Punk ⏮ ⏸ ⏭ │ 🔀 🔁 [📻 DJ] │ ▂▃▅▆▇▆▄▃▂▂▃▄▅ [y] Lyrics [u] Queue 🔊 85% │
│ 02:14 ━━━●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 05:20 (Click to Seek) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```
@@ -98,15 +101,16 @@ Configuration is automatically saved to:
|:---|:---|:---|
| Space | Play / Pause toggle | Click `[ ▶ / ⏸ ]` |
| n / p | Next / Previous track | Click `[ ⏭ ]` / `[ ⏮ ]` |
-| Enter | Play selected track / Expand album | Double-click track |
-| j / ↓ | Move down | Scroll wheel down |
-| k / ↑ | Move up | Scroll wheel up |
-| h / l / Tab | Switch between Artists, Albums, and Tracks | Click pane |
+| a | Toggle **Auto-DJ** (Infinite Library Radio) | Click `[ 📻 DJ ]` |
| s | Toggle Shuffle mode | Click `[ 🔀 ]` |
| r | Toggle Repeat mode (*Off* ➔ *All* ➔ *One*) | Click `[ 🔁 ]` |
| f / L | Star / Favorite track (Like) | Click `[ ❤️ ]` |
| y | Toggle Lyrics screen | Click `[y] Lyrics` |
| u | Toggle Queue screen | Click `[u] Queue` |
+| Enter | Play selected track / Expand album | Double-click track |
+| j / ↓ | Move down | Scroll wheel down |
+| k / ↑ | Move up | Scroll wheel up |
+| h / l / Tab | Switch between Artists, Albums, and Tracks | Click pane |
| + / = / ] | Volume Up (+5%) | Click / Drag Volume slider |
| - / _ / [ | Volume Down (-5%) | Click / Drag Volume slider |
| t | Cycle Color Theme | Click `[t] Theme` |
diff --git a/src/app.rs b/src/app.rs
index 62b9bcf..223fd10 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -96,6 +96,7 @@ pub struct LayoutCache {
pub btn_next: Rect,
pub btn_shuffle: Rect,
pub btn_repeat: Rect,
+ pub btn_auto_dj: Rect,
pub btn_star: Rect,
pub btn_lyrics: Rect,
pub progress_bar: Rect,
@@ -122,6 +123,10 @@ pub enum AppAction {
LoadedArtists(Vec),
LoadedAlbums(Vec),
LoadedTracks(Vec),
+ AutoDjLoaded {
+ songs: Vec,
+ start_playing: bool,
+ },
AudioBuffered {
bytes: Bytes,
meta: TrackMetadata,
@@ -164,11 +169,13 @@ pub struct App {
pub tracks: Vec,
pub tracks_state: ListState,
- // Queue & Playback modes
+ // Queue, Auto-DJ & Playback modes
pub queue: Vec,
pub queue_index: usize,
pub shuffle: bool,
pub repeat_mode: RepeatMode,
+ pub auto_dj: bool,
+ pub is_fetching_auto_dj: bool,
pub was_playing_before: bool,
pub scrobble_submitted: bool,
@@ -245,6 +252,8 @@ impl App {
queue_index: 0,
shuffle: false,
repeat_mode: RepeatMode::Off,
+ auto_dj: false,
+ is_fetching_auto_dj: false,
was_playing_before: false,
scrobble_submitted: false,
cover_thumbnail: None,
@@ -307,6 +316,17 @@ impl App {
self.tracks_state.select(Some(0));
}
}
+ AppAction::AutoDjLoaded { songs, start_playing } => {
+ self.is_loading = false;
+ self.is_fetching_auto_dj = false;
+ let was_empty = self.queue.is_empty();
+ self.queue.extend(songs);
+ if start_playing || was_empty {
+ self.queue_index = 0;
+ self.play_queue_index(0);
+ }
+ self.set_toast("📻 Auto-DJ: Queued random tracks from library!");
+ }
AppAction::AudioBuffered { bytes, meta } => {
self.is_loading = false;
self.audio_cache.insert(meta.id.clone(), bytes.clone());
@@ -343,10 +363,12 @@ impl App {
}
AppAction::ConnectionFailed(err) => {
self.is_loading = false;
+ self.is_fetching_auto_dj = false;
self.settings_form.status_message = Some((format!("Connection failed: {err}"), true));
}
AppAction::Error(err) => {
self.is_loading = false;
+ self.is_fetching_auto_dj = false;
self.set_toast(format!("Error: {}", err));
}
AppAction::Status(msg) => {
@@ -374,22 +396,34 @@ impl App {
}
}
+ // Auto-DJ: Replenish queue if low on tracks
+ if self.auto_dj && !self.is_fetching_auto_dj {
+ let remaining = self.queue.len().saturating_sub(self.queue_index);
+ if remaining <= 2 {
+ self.fetch_auto_dj_songs(false);
+ }
+ }
+
// Auto Next Track on track end
if self.was_playing_before && progress.state == PlaybackState::Stopped {
self.was_playing_before = false;
- match self.repeat_mode {
- RepeatMode::Track => {
- self.play_queue_index(self.queue_index);
- }
- RepeatMode::All => {
- if !self.queue.is_empty() {
- let next_idx = (self.queue_index + 1) % self.queue.len();
- self.play_queue_index(next_idx);
+ if self.auto_dj {
+ self.next_track();
+ } else {
+ match self.repeat_mode {
+ RepeatMode::Track => {
+ self.play_queue_index(self.queue_index);
}
- }
- RepeatMode::Off => {
- if self.queue_index + 1 < self.queue.len() {
- self.play_queue_index(self.queue_index + 1);
+ RepeatMode::All => {
+ if !self.queue.is_empty() {
+ let next_idx = (self.queue_index + 1) % self.queue.len();
+ self.play_queue_index(next_idx);
+ }
+ }
+ RepeatMode::Off => {
+ if self.queue_index + 1 < self.queue.len() {
+ self.play_queue_index(self.queue_index + 1);
+ }
}
}
}
@@ -406,6 +440,42 @@ impl App {
}
}
+ pub fn toggle_auto_dj(&mut self) {
+ self.auto_dj = !self.auto_dj;
+ if self.auto_dj {
+ self.set_toast("📻 Auto-DJ: ON (Continuous library radio)");
+ self.fetch_auto_dj_songs(true);
+ } else {
+ self.set_toast("📻 Auto-DJ: OFF (Standard queue mode)");
+ }
+ }
+
+ pub fn fetch_auto_dj_songs(&mut self, start_playing: bool) {
+ if self.is_fetching_auto_dj {
+ return;
+ }
+ self.is_fetching_auto_dj = true;
+ self.is_loading = true;
+ self.loading_msg = "📻 Auto-DJ: Fetching random songs from library...".to_string();
+
+ let client = self.client.clone();
+ let tx = self.action_tx.clone();
+
+ tokio::spawn(async move {
+ match client.get_random_songs(30).await {
+ Ok(songs) => {
+ let _ = tx.send(AppAction::AutoDjLoaded {
+ songs,
+ start_playing,
+ });
+ }
+ Err(e) => {
+ let _ = tx.send(AppAction::Error(format!("Auto-DJ error: {e}")));
+ }
+ }
+ });
+ }
+
pub fn handle_mouse_click(&mut self, x: u16, y: u16) {
// Modal mode (Setup / Settings)
if self.mode == AppMode::Setup || self.mode == AppMode::Settings {
@@ -480,6 +550,9 @@ impl App {
} else if hit(self.layout.btn_repeat, x, y) {
self.toggle_repeat();
return;
+ } else if hit(self.layout.btn_auto_dj, x, y) {
+ self.toggle_auto_dj();
+ return;
} else if hit(self.layout.btn_star, x, y) {
self.toggle_star();
return;
@@ -638,6 +711,20 @@ impl App {
let tx = self.action_tx.clone();
let album_id = album.id.clone();
+ // Also fetch album cover art preview in sidebar
+ if let Some(ref cover_id) = album.cover_art {
+ let c = client.clone();
+ let cid = cover_id.clone();
+ let t = tx.clone();
+ tokio::spawn(async move {
+ if let Ok(bytes) = c.download_cover_art(&cid, Some(100)).await {
+ if let Some(lines) = render_cover_thumbnail(&bytes, 20, 8) {
+ let _ = t.send(AppAction::CoverLoaded(lines));
+ }
+ }
+ });
+ }
+
tokio::spawn(async move {
match client.get_album(&album_id).await {
Ok(details) => {
@@ -712,7 +799,7 @@ impl App {
let t = tx.clone();
tokio::spawn(async move {
if let Ok(bytes) = c.download_cover_art(&cover_id, Some(100)).await {
- if let Some(lines) = render_cover_thumbnail(&bytes, 16, 8) {
+ if let Some(lines) = render_cover_thumbnail(&bytes, 20, 8) {
let _ = t.send(AppAction::CoverLoaded(lines));
}
}
diff --git a/src/main.rs b/src/main.rs
index 64a52d6..3db38ce 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -210,6 +210,9 @@ fn handle_key(app: &mut App, key: KeyCode) {
KeyCode::Char('r') => {
app.toggle_repeat();
}
+ KeyCode::Char('a') => {
+ app.toggle_auto_dj();
+ }
KeyCode::Char('f') | KeyCode::Char('L') => {
app.toggle_star();
}
diff --git a/src/subsonic/client.rs b/src/subsonic/client.rs
index 250dbe4..6ddd2ae 100644
--- a/src/subsonic/client.rs
+++ b/src/subsonic/client.rs
@@ -240,4 +240,14 @@ impl SubsonicClient {
Ok(None)
}
+
+ /// Get random songs from entire library (Auto-DJ / Radio mode)
+ pub async fn get_random_songs(&self, size: u32) -> Result> {
+ let size_str = size.to_string();
+ let resp = self.get_json("getRandomSongs", &[("size", size_str.as_str())]).await?;
+ resp.response
+ .random_songs
+ .map(|r| r.song)
+ .context("Random songs missing in getRandomSongs response")
+ }
}
diff --git a/src/subsonic/models.rs b/src/subsonic/models.rs
index 3284cad..8105fa9 100644
--- a/src/subsonic/models.rs
+++ b/src/subsonic/models.rs
@@ -28,6 +28,8 @@ pub struct SubsonicResponseBody {
pub lyrics: Option,
#[serde(rename = "lyricsList")]
pub lyrics_list: Option,
+ #[serde(rename = "randomSongs")]
+ pub random_songs: Option,
#[serde(rename = "searchResult3")]
pub search_result3: Option,
}
@@ -154,6 +156,12 @@ pub struct ChildSong {
pub starred: Option,
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct RandomSongs {
+ #[serde(default)]
+ pub song: Vec,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult3 {
#[serde(default)]
diff --git a/src/ui/cover.rs b/src/ui/cover.rs
index c1d1b06..6fb1aaa 100644
--- a/src/ui/cover.rs
+++ b/src/ui/cover.rs
@@ -1,13 +1,15 @@
use bytes::Bytes;
use image::imageops::FilterType;
use image::{GenericImageView, Rgba};
-use ratatui::style::{Color, Style};
+use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
+use crate::ui::theme::Theme;
+
/// Convert image bytes (JPEG / PNG) into Unicode Half-block terminal spans
pub fn render_cover_thumbnail(bytes: &Bytes, width: u32, height: u32) -> Option>> {
let img = image::load_from_memory(bytes).ok()?;
-
+
// Terminal cells are approx 1:2 aspect ratio, so height in cells corresponds to 2x pixel rows
let pixel_height = height * 2;
let resized = img.resize_exact(width, pixel_height, FilterType::Triangle);
@@ -41,3 +43,42 @@ pub fn render_cover_thumbnail(bytes: &Bytes, width: u32, height: u32) -> Option<
Some(lines)
}
+
+/// Fallback vinyl / cassette art when no cover image is downloaded yet
+pub fn render_vinyl_fallback(theme: &Theme, is_playing: bool, phase: usize) -> Vec> {
+ let spinner = if is_playing {
+ match (phase / 3) % 4 {
+ 0 => "◜◝",
+ 1 => "◝◞",
+ 2 => "◞◟",
+ _ => "◟◜",
+ }
+ } else {
+ "●●"
+ };
+
+ vec![
+ Line::from(""),
+ Line::from(vec![
+ Span::styled(" .───. ", Style::default().fg(theme.primary)),
+ ]),
+ Line::from(vec![
+ Span::styled(" / ", Style::default().fg(theme.primary)),
+ Span::styled(spinner, Style::default().fg(theme.accent).add_modifier(Modifier::BOLD)),
+ Span::styled(" \\ ", Style::default().fg(theme.primary)),
+ ]),
+ Line::from(vec![
+ Span::styled(" | ( O ) | ", Style::default().fg(theme.secondary).add_modifier(Modifier::BOLD)),
+ ]),
+ Line::from(vec![
+ Span::styled(" \\ / ", Style::default().fg(theme.primary)),
+ ]),
+ Line::from(vec![
+ Span::styled(" '───' ", Style::default().fg(theme.primary)),
+ ]),
+ Line::from(""),
+ Line::from(vec![
+ Span::styled(" 🎵 Vinyl Record ", Style::default().fg(theme.text_muted)),
+ ]),
+ ]
+}
diff --git a/src/ui/widgets/library.rs b/src/ui/widgets/library.rs
index 2141033..ce3347e 100644
--- a/src/ui/widgets/library.rs
+++ b/src/ui/widgets/library.rs
@@ -1,20 +1,28 @@
-use ratatui::layout::{Constraint, Direction, Layout, Rect};
+use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
-use ratatui::widgets::{Block, Borders, List, ListItem};
+use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
use ratatui::Frame;
use crate::app::{App, FocusPane};
+use crate::audio::PlaybackState;
+use crate::ui::cover::render_vinyl_fallback;
pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
let theme = &app.theme;
- // Split horizontally: Left (Artists), Right (Albums + Tracks)
+ // Split horizontally: Left (Artists + Cover Art), Right (Albums + Tracks)
let chunks = Layout::default()
.direction(Direction::Horizontal)
- .constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
+ .constraints([Constraint::Percentage(32), Constraint::Percentage(68)])
.split(area);
+ // Split left column vertically: Top (Artists List), Bottom (Now Playing & Cover Art)
+ let left_chunks = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([Constraint::Percentage(55), Constraint::Percentage(45)])
+ .split(chunks[0]);
+
// Split right column vertically: Top (Albums), Bottom (Tracks)
let right_chunks = Layout::default()
.direction(Direction::Vertical)
@@ -22,7 +30,7 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
.split(chunks[1]);
// Cache hitboxes for mouse interaction
- app.layout.artists_pane = chunks[0];
+ app.layout.artists_pane = left_chunks[0];
app.layout.albums_pane = right_chunks[0];
app.layout.tracks_pane = right_chunks[1];
@@ -60,9 +68,34 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
.highlight_style(theme.item_selected_style())
.highlight_symbol("▶ ");
- frame.render_stateful_widget(artists_list, chunks[0], &mut app.artists_state);
+ frame.render_stateful_widget(artists_list, left_chunks[0], &mut app.artists_state);
- // 2. Albums List
+ // 2. Cover Art & Now Playing Sidebar Pane
+ let progress = app.player.get_progress();
+ let is_playing = progress.state == PlaybackState::Playing;
+
+ let cover_block = Block::default()
+ .borders(Borders::ALL)
+ .border_type(theme.border_type)
+ .border_style(Style::default().fg(theme.border_focused))
+ .title(Span::styled(" 🖼️ Now Playing & Cover ", theme.title_style()))
+ .title_alignment(Alignment::Center);
+
+ let cover_inner = cover_block.inner(left_chunks[1]);
+ frame.render_widget(cover_block, left_chunks[1]);
+
+ if let Some(ref cover_lines) = app.cover_thumbnail {
+ // Display full TrueColor Unicode Half-block album cover
+ let cover_widget = Paragraph::new(cover_lines.clone()).alignment(Alignment::Center);
+ frame.render_widget(cover_widget, cover_inner);
+ } else {
+ // Fallback vinyl turntable animation
+ let vinyl_lines = render_vinyl_fallback(&app.theme, is_playing, app.marquee_tick);
+ let vinyl_widget = Paragraph::new(vinyl_lines).alignment(Alignment::Center);
+ frame.render_widget(vinyl_widget, cover_inner);
+ }
+
+ // 3. Albums List
let is_albums_focused = app.focus == FocusPane::Albums;
let albums_border_style = if is_albums_focused {
theme.focused_border_style()
@@ -101,7 +134,7 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
frame.render_stateful_widget(albums_list, right_chunks[0], &mut app.albums_state);
- // 3. Tracks List
+ // 4. Tracks List
let is_tracks_focused = app.focus == FocusPane::Tracks;
let tracks_border_style = if is_tracks_focused {
theme.focused_border_style()
@@ -109,7 +142,6 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
theme.normal_border_style()
};
- let progress = app.player.get_progress();
let current_playing_id = progress.current_track.as_ref().map(|t| &t.id);
let track_items: Vec = app
diff --git a/src/ui/widgets/modals.rs b/src/ui/widgets/modals.rs
index bbb3232..72b5151 100644
--- a/src/ui/widgets/modals.rs
+++ b/src/ui/widgets/modals.rs
@@ -40,7 +40,8 @@ pub fn render_help(app: &App, frame: &mut Frame, area: Rect) {
("n / p", "Next / Previous track in queue"),
("s", "Toggle Shuffle mode"),
("r", "Toggle Repeat mode (Off / Track / All)"),
- ("l", "Star / Favorite track (Like)"),
+ ("a", "Toggle Auto-DJ (Infinite Library Radio)"),
+ ("f / L", "Star / Favorite track (Like)"),
("y", "Toggle Lyrics (Text/Synced)"),
("u", "Toggle Queue view"),
("+ / = / ]", "Increase volume (+5%)"),
diff --git a/src/ui/widgets/player_bar.rs b/src/ui/widgets/player_bar.rs
index 1635c43..3d11078 100644
--- a/src/ui/widgets/player_bar.rs
+++ b/src/ui/widgets/player_bar.rs
@@ -39,7 +39,7 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
])
.split(rows[0]);
- // 1. Controls on center left: [⏮] [▶ / ⏸] [⏭] [🔀] [🔁]
+ // 1. Controls on center left: [⏮] [▶ / ⏸] [⏭] [🔀] [🔁] [📻 DJ]
let ctrl_x = top_cols[1].x;
let ctrl_y = top_cols[1].y;
@@ -48,6 +48,7 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
app.layout.btn_next = Rect::new(ctrl_x + 8, ctrl_y, 4, 1);
app.layout.btn_shuffle = Rect::new(ctrl_x + 13, ctrl_y, 4, 1);
app.layout.btn_repeat = Rect::new(ctrl_x + 17, ctrl_y, 4, 1);
+ app.layout.btn_auto_dj = Rect::new(ctrl_x + 22, ctrl_y, 7, 1);
// 2. Track Info with Marquee
let is_starred = progress
@@ -103,6 +104,12 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
Style::default().fg(theme.text_muted)
};
+ let auto_dj_style = if app.auto_dj {
+ Style::default().bg(theme.accent).fg(theme.surface).add_modifier(Modifier::BOLD)
+ } else {
+ Style::default().fg(theme.text_muted)
+ };
+
let mut center_spans = vec![
Span::styled("⏮ ", Style::default().fg(theme.primary)),
Span::styled(format!("{play_icon} "), Style::default().fg(theme.highlight_fg).add_modifier(Modifier::BOLD)),
@@ -110,10 +117,11 @@ pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
Span::styled("│ ", Style::default().fg(theme.border)),
Span::styled("🔀 ", shuffle_style),
Span::styled(format!("{repeat_icon} "), repeat_style),
+ Span::styled("[📻 DJ] ", auto_dj_style),
Span::styled("│ ", Style::default().fg(theme.border)),
];
- let used_center_width = 22;
+ let used_center_width = 30;
let vis_width = (top_cols[1].width as usize).saturating_sub(used_center_width);
if app.config.enable_visualizer && vis_width > 4 {
let mut vis_spans = app.visualizer.render_colored_spans(vis_width);